From 34ad17509d448e16b4dc168c1fc32c4d24ca00b2 Mon Sep 17 00:00:00 2001 From: Ninym Date: Sat, 18 Apr 2026 15:48:00 +0200 Subject: [PATCH 1/6] chore(ci): add semgrep rule no-bracket-assign-on-literal-object-map --- .semgrep/rules/no-bracket-assign-hot-paths.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .semgrep/rules/no-bracket-assign-hot-paths.yml diff --git a/.semgrep/rules/no-bracket-assign-hot-paths.yml b/.semgrep/rules/no-bracket-assign-hot-paths.yml new file mode 100644 index 00000000..4c4b0b14 --- /dev/null +++ b/.semgrep/rules/no-bracket-assign-hot-paths.yml @@ -0,0 +1,17 @@ +rules: + - id: no-bracket-assign-on-literal-object-map + languages: [typescript] + severity: ERROR + message: > + Bracket-assign accumulator on a map created with `{}` allows prototype + pollution if the key comes from external data. Initialize the map with + `Object.create(null)` instead. + patterns: + - pattern: $MAP[$KEY] = $MAP[$KEY] ?? $INIT + - pattern-not-inside: | + const $MAP = Object.create(null) + ... + paths: + include: + - src/providers/*.ts + - src/parser.ts From c2c67b0faa2b79b60c7e2ab5ca60fba9cf9db112 Mon Sep 17 00:00:00 2001 From: Ninym Date: Sat, 18 Apr 2026 15:48:00 +0200 Subject: [PATCH 2/6] chore(ci): add workflow running semgrep bracket-assign guard on push/PR --- .github/workflows/ci.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..9acd56a4 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,20 @@ +name: CI + +on: + push: + branches: ['**'] + pull_request: + +jobs: + semgrep: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install Semgrep + run: pip install semgrep + + - name: Run Semgrep bracket-assign guard + run: | + semgrep --config .semgrep/rules/no-bracket-assign-hot-paths.yml \ + src/providers/ src/parser.ts --error From d52fef30ee634cbfd1d861f9b0d9e5317f0ae9d1 Mon Sep 17 00:00:00 2001 From: Ninym Date: Sat, 18 Apr 2026 15:48:00 +0200 Subject: [PATCH 3/6] fix(parser): use Object.create(null) for categoryBreakdown map --- src/parser.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parser.ts b/src/parser.ts index 4adcf3bc..ba0d31c6 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -173,7 +173,7 @@ function buildSessionSummary( const toolBreakdown: SessionSummary['toolBreakdown'] = Object.create(null) const mcpBreakdown: SessionSummary['mcpBreakdown'] = Object.create(null) const bashBreakdown: SessionSummary['bashBreakdown'] = Object.create(null) - const categoryBreakdown: SessionSummary['categoryBreakdown'] = {} as SessionSummary['categoryBreakdown'] + const categoryBreakdown: SessionSummary['categoryBreakdown'] = Object.create(null) let totalCost = 0 let totalInput = 0 From 9ebb7f5a4fd7952932f020b18c3e4505228d0dd9 Mon Sep 17 00:00:00 2001 From: Ninym Date: Sat, 18 Apr 2026 15:48:00 +0200 Subject: [PATCH 4/6] chore(ci): expand semgrep rule to cover ||, ??=, and if-guard variants --- .semgrep/rules/no-bracket-assign-hot-paths.yml | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/.semgrep/rules/no-bracket-assign-hot-paths.yml b/.semgrep/rules/no-bracket-assign-hot-paths.yml index 4c4b0b14..e2e633da 100644 --- a/.semgrep/rules/no-bracket-assign-hot-paths.yml +++ b/.semgrep/rules/no-bracket-assign-hot-paths.yml @@ -3,15 +3,20 @@ rules: languages: [typescript] severity: ERROR message: > - Bracket-assign accumulator on a map created with `{}` allows prototype - pollution if the key comes from external data. Initialize the map with + Bracket-assign on a map created with `{}` allows prototype pollution when + the key comes from external data. Initialize the map with `Object.create(null)` instead. patterns: - - pattern: $MAP[$KEY] = $MAP[$KEY] ?? $INIT + - pattern-either: + - pattern: $MAP[$KEY] = $MAP[$KEY] ?? $INIT + - pattern: $MAP[$KEY] = $MAP[$KEY] || $INIT + - pattern: $MAP[$KEY] ??= $INIT + - pattern: | + if (!$MAP[$KEY]) $MAP[$KEY] = $INIT - pattern-not-inside: | const $MAP = Object.create(null) ... paths: include: - - src/providers/*.ts - - src/parser.ts + - '/src/providers/*.ts' + - '/src/parser.ts' From 823702185936be6ee5ffdca4ff0619f4970ab9ae Mon Sep 17 00:00:00 2001 From: Ninym Date: Sat, 18 Apr 2026 15:48:00 +0200 Subject: [PATCH 5/6] chore(ci): limit push trigger to main and add semgrep --strict --- .github/workflows/ci.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9acd56a4..c97b5810 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,7 +2,7 @@ name: CI on: push: - branches: ['**'] + branches: [main] pull_request: jobs: @@ -17,4 +17,5 @@ jobs: - name: Run Semgrep bracket-assign guard run: | semgrep --config .semgrep/rules/no-bracket-assign-hot-paths.yml \ - src/providers/ src/parser.ts --error + --error --strict \ + src/providers/ src/parser.ts From cfb2192e2686ac6c7673aaef4116458a835c27c4 Mon Sep 17 00:00:00 2001 From: Ninym Date: Sat, 18 Apr 2026 15:48:01 +0200 Subject: [PATCH 6/6] chore(ci): use jq to enforce finding count (--error unreliable in semgrep 1.x) --- .github/workflows/ci.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c97b5810..5233243a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,6 +16,12 @@ jobs: - name: Run Semgrep bracket-assign guard run: | + set -e semgrep --config .semgrep/rules/no-bracket-assign-hot-paths.yml \ - --error --strict \ - src/providers/ src/parser.ts + --strict --json \ + src/providers/ src/parser.ts > semgrep-out.json + FINDINGS=$(jq '.results | length' semgrep-out.json) + if [ "$FINDINGS" -gt 0 ]; then + jq -r '.results[] | "::error file=\(.path),line=\(.start.line)::\(.extra.message)"' semgrep-out.json + exit 1 + fi