Cover JavaScript taint throw reachability
ober
1d6987f3009b1ccc8e4ec0d7ed7219a4ece35d81
--- a/HANDOFF_OPUS_4_8.md +++ b/HANDOFF_OPUS_4_8.md @@ -1,218 +1,174 @@ # Opus 4.8 Handoff: jerboa-semgrep Semgrep Parity -Date: 2026-05-30 04:23 MDT +Date: 2026-05-30 05:16 MDT Workspace: `/Users/user/mine/jerboa-semgrep` Sibling upstream Semgrep checkout: `/Users/user/mine/semgrep` Packaged Semgrep oracle: `/Users/user/.local/bin/semgrep` -Base HEAD before this checkpoint: -`403a73a Cover JavaScript JSX and XML fixtures` +Base HEAD before this checkpoint: `4e0eedf Cover JavaScript deep pattern fixtures` The user wants this project carried forward until the pure Jerboa port reaches -Semgrep parity. Do not treat this handoff as completion. This checkpoint closes -the remaining JavaScript `.sgrep` pattern-fixture mismatches from the upstream -`tests/patterns/js` corpus, but Semgrep parity is still broader than these -fixture sweeps. +Semgrep parity. Do not treat this handoff as completion. This checkpoint moves +the next parity frontier from Python/JavaScript pattern fixtures into upstream +JavaScript taint rules and fixes one concrete semantic mismatch there. ## Current Headline -- Full Python pattern sweep: 164 passed / 0 mismatched. -- Full JavaScript pattern sweep: 132 passed / 0 mismatched. This improved from - 117 passed / 15 mismatched by closing the caching, deep statement/expression, - JSX deep-expression, and method-chain-vs-statement frontier. -- Promoted JavaScript oracle slice: 91 passed / 0 mismatched. -- Promoted Python oracle slice: 116 passed / 0 mismatched. +- Full Python `.sgrep` pattern sweep: 164 passed / 0 mismatched. +- Full JavaScript `.sgrep` pattern sweep: 132 passed / 0 mismatched. +- Promoted JavaScript pattern oracle: 91 passed / 0 mismatched. +- Promoted Python pattern oracle: 116 passed / 0 mismatched. - Local oracle: 42 passed / 0 failed. -- Broad same-basename upstream sweep: 437 passed / 0 mismatched / 0 Jerboa - errors, with 3 current-Semgrep oracle errors. -- Smoke suite: 300 tests, 300 passed, 0 failed. -- Semgrep parity is not reached yet. Pattern fixtures for Python and - JavaScript are now clean, but the project still needs broader parser, - matcher, rule-validation, taint/dataflow, target-selection, CLI, output, - autofix, and corpus compatibility. - -## Recent Checkpoint Chain - -Recent commits/checkpoints that matter for the next agent: - -- `2abb070 Cover JavaScript import equivalence fixtures`: committed. It - promoted the 13-case import/CommonJS equivalence cluster and left the full JS - sweep at 82 passed / 50 mismatched. -- `1de036c Cover JavaScript singleton pattern fixtures`: committed. It - promoted ten small JS singleton fixture cases and left the full JS sweep at - 92 passed / 40 mismatched. -- `2cda4de Cover JavaScript decorator and catch fixtures`: committed. It - promoted six catch/decorator/export cases and left the full JS sweep at - 98 passed / 34 mismatched. -- `1b617d3 Cover JavaScript class and dependency fixtures`: committed. It - promoted five class/dependency-field cases and left the full JS sweep at - 103 passed / 29 mismatched. -- `88336fa Cover JavaScript arrow and function fixtures`: committed. It - promoted three arrow/function cases and left the full JS sweep at - 106 passed / 26 mismatched. -- `94d853d Cover JavaScript equality and AC fixtures`: committed. It promoted - three equality/range/AC cases and left the full JS sweep at - 109 passed / 23 mismatched. -- `403a73a Cover JavaScript JSX and XML fixtures`: committed. It promoted - eight JSX/XML cases and left the full JS sweep at - 117 passed / 15 mismatched. -- This checkpoint: commit as `Cover JavaScript deep pattern fixtures`. It - promotes 15 caching/deep/method-chain cases and leaves the full JS pattern - sweep at 132 passed / 0 mismatched. - -The current checkpoint modifies only the Jerboa scanner, generated scanner -artifact, promoted oracle regex, smoke tests, generated build hashes, and this -handoff. No Semgrep upstream files were changed. - -## Project Contract - -`what.md` is still the project contract: - -- Replace Semgrep's OCaml and Python implementation with ordinary Jerboa `.ss` - modules. -- Preserve existing Semgrep rules unchanged. -- Preserve Semgrep-compatible scan behavior and output formats. -- Keep the result embeddable in Jerboa applications. - -The implementation is still a compatibility work in progress. Python and -JavaScript `.sgrep` pattern fixtures are green, but full Semgrep parity also -requires broader rule, taint, target-selection, parser, output, autofix, and -CLI compatibility beyond these fixture slices. - -## Files In This Checkpoint +- Smoke suite: 301 tests, 301 passed, 0 failed. +- Broad same-basename upstream `tests/rules` sweep: 437 passed / 0 mismatched + / 0 Jerboa errors, with 3 packaged-Semgrep current errors. +- New upstream `tests/tainting_rules/js` sweep: 5 passed / 6 mismatched / + 0 Jerboa errors / 0 current errors. This improved from 4 passed / 7 + mismatched by matching Semgrep's handling of unreachable JS taint sinks after + `throw`. + +Semgrep parity is not reached. Python and JavaScript pattern fixtures are clean, +but taint/dataflow, parser compatibility, rule validation, CLI behavior, output, +autofix, target selection, ignore handling, and broader upstream test corpora +remain open. + +## What Changed In This Checkpoint This checkpoint modifies: ```text -Makefile HANDOFF_OPUS_4_8.md src/semgrep/scan.ss lib/semgrep/scan.sls src/.jerbuild-hashes +tests/oracle/normalize-findings.ss tests/smoke.ss ``` `lib/semgrep/scan.sls` and `src/.jerbuild-hashes` are generated by -`make build`/`make test`, but they are tracked and should be committed with the -matching source changes. +`make build`/`make test`, but they are tracked and must stay in the commit with +the matching `src/semgrep/scan.ss` changes. -Expected post-commit state: +### Oracle Normalization -```sh -git status --short --branch -``` - -should show a clean `main` branch. - -## Verified This Checkpoint - -All commands below were run from `/Users/user/mine/jerboa-semgrep`. - -Build: - -```sh -make build -``` - -Result: +`tests/oracle/normalize-findings.ss` now strips check-id prefixes emitted by +packaged Semgrep for rules under `tests/tainting_rules`, including language +subdirectories such as: ```text -jerbuild: 14 processed, 0 skipped, 0 errors +Users.user.mine.semgrep.tests.tainting_rules.js. +tests.tainting_rules.js. ``` -Focused JavaScript caching/deep-condition slice: +Without this normalization, `tests/tainting_rules/js` appeared to have extra +mismatches where the only difference was a check id like +`Users.user.mine.semgrep.tests.tainting_rules.js.taint-test` versus +`taint-test`. -```sh -SEMGREP_CURRENT=/Users/user/.local/bin/semgrep PATTERN_LANGS=js CASE_REGEX='^(caching_deep|caching_deep_metavar|caching_different_depth|caching_flat|caching_nested|deep_cond)$' LIST_MISMATCHES=1 MAX_DIFFS=200 tests/oracle/patterns-sweep.sh -``` +### JavaScript Taint Reachability -Result: +`src/semgrep/scan.ss` now has a generic taint reachability wrapper: -```text -patterns-sweep: 6 passed, 0 mismatched, 0 jerboa errors, 0 current errors, 0 skipped, 6 compared -``` - -Focused JavaScript deep statement/expression slice: - -```sh -SEMGREP_CURRENT=/Users/user/.local/bin/semgrep PATTERN_LANGS=js CASE_REGEX='^(deep_deep_stmt2|deep_expr_and_ellipsis|deep_expr_vs_statement|deep_stmt|deep_stmt2|misc_deep_expr_stmt|misc_method_chain_vs_stmt)$' LIST_MISMATCHES=1 MAX_DIFFS=260 tests/oracle/patterns-sweep.sh -``` +- `javascript-taint-line-opens-unreachable?` +- `javascript-taint-unreachable-finding?` +- `filter-javascript-taint-reachable-findings` +- `filter-taint-reachable-findings` -Result: +The wrapper is applied wherever taint specs are scanned: -```text -patterns-sweep: 7 passed, 0 mismatched, 0 jerboa errors, 0 current errors, 0 skipped, 7 compared -``` +- sources and source matches +- sinks +- explicit propagators +- implicit assignment propagators +- sanitizers through `scan-taint-specs` -Focused final JavaScript deep fixtures: +The JavaScript filter currently treats only a leading `throw ...` line as +opening an unreachable region until the next line that starts with `}`. This is +deliberately narrower than the existing constant-propagation helper. A first +attempt also treated plain `return ...` as unreachable, but that was too coarse: +it suppressed valid taint findings inside multiline return expressions and +returned callbacks. The regression was caught by upstream +`tests/rules/taint_object_destructure`. Future `return` support should be +statement-aware, not line-only. -```sh -SEMGREP_CURRENT=/Users/user/.local/bin/semgrep PATTERN_LANGS=js CASE_REGEX='^(deep_deep_stmt1|deep_expr_xml)$' LIST_MISMATCHES=1 MAX_DIFFS=260 tests/oracle/patterns-sweep.sh -``` +### Smoke Coverage -Result: +`tests/smoke.ss` adds: ```text -patterns-sweep: 2 passed, 0 mismatched, 0 jerboa errors, 0 current errors, 0 skipped, 2 compared +scan JavaScript taint ignores unreachable after throw ``` -Smoke suite: +It checks that: -```sh -make test +```javascript +if (random()) { + sink(source); +} else { + throw "error"; + sink(source); +} ``` -Result: +reports only the reachable sink. -```text -300 tests, 300 passed, 0 failed -``` +## Verification Run + +All commands below were run from `/Users/user/mine/jerboa-semgrep`. -Promoted JavaScript oracle: +Smoke: ```sh -SEMGREP_CURRENT=/Users/user/.local/bin/semgrep make patterns-js-oracle +make test ``` Result: ```text -patterns-sweep: 91 passed, 0 mismatched, 0 jerboa errors, 0 current errors, 0 skipped, 91 compared +301 tests, 301 passed, 0 failed ``` -Full upstream JavaScript pattern sweep: +Focused upstream JS taint `throw` case: ```sh -SEMGREP_CURRENT=/Users/user/.local/bin/semgrep PATTERN_LANGS=js LIST_MISMATCHES=1 MAX_DIFFS=80 tests/oracle/patterns-sweep.sh +SEMGREP_CURRENT=/Users/user/.local/bin/semgrep \ +UPSTREAM_RULE_DIR=/Users/user/mine/semgrep/tests/tainting_rules/js \ +CASE_REGEX='^(throw)$' LIST_MISMATCHES=1 MAX_DIFFS=80 \ +tests/oracle/upstream-sweep.sh ``` Result: ```text -patterns-sweep: 132 passed, 0 mismatched, 0 jerboa errors, 0 current errors, 0 skipped, 132 compared +upstream-sweep: 1 passed, 0 mismatched, 0 jerboa errors, 0 current errors, 1 compared ``` -Promoted Python oracle: +Focused regression guard for the rejected `return` implementation: ```sh -SEMGREP_CURRENT=/Users/user/.local/bin/semgrep make patterns-oracle +SEMGREP_CURRENT=/Users/user/.local/bin/semgrep \ +UPSTREAM_RULE_DIR=/Users/user/mine/semgrep/tests/rules \ +CASE_REGEX='^(taint_object_destructure)$' LIST_MISMATCHES=1 MAX_DIFFS=80 \ +tests/oracle/upstream-sweep.sh ``` Result: ```text -patterns-sweep: 116 passed, 0 mismatched, 0 jerboa errors, 0 current errors, 0 skipped, 116 compared +upstream-sweep: 1 passed, 0 mismatched, 0 jerboa errors, 0 current errors, 1 compared ``` -Full upstream Python pattern sweep: +Full upstream JS tainting-rule subdirectory: ```sh -SEMGREP_CURRENT=/Users/user/.local/bin/semgrep PATTERN_LANGS=python LIST_MISMATCHES=1 MAX_DIFFS=60 tests/oracle/patterns-sweep.sh +SEMGREP_CURRENT=/Users/user/.local/bin/semgrep \ +UPSTREAM_RULE_DIR=/Users/user/mine/semgrep/tests/tainting_rules/js \ +LIST_MISMATCHES=1 MAX_DIFFS=160 tests/oracle/upstream-sweep.sh ``` Result: ```text -patterns-sweep: 164 passed, 0 mismatched, 0 jerboa errors, 0 current errors, 0 skipped, 164 compared +upstream-sweep: 5 passed, 6 mismatched, 0 jerboa errors, 0 current errors, 11 compared ``` Local oracle: @@ -227,10 +183,12 @@ Result: oracle: 42 passed, 0 failed ``` -Broad same-basename upstream sweep: +Broad same-basename upstream `tests/rules` sweep: ```sh -SEMGREP_CURRENT=/Users/user/.local/bin/semgrep tests/oracle/upstream-sweep.sh +SEMGREP_CURRENT=/Users/user/.local/bin/semgrep \ +UPSTREAM_RULE_DIR=/Users/user/mine/semgrep/tests/rules \ +LIST_MISMATCHES=1 MAX_DIFFS=40 tests/oracle/upstream-sweep.sh ``` Result: @@ -243,276 +201,118 @@ The three current-side errors are packaged-Semgrep schema failures for `anywhere_global`, `anywhere_include`, and `anywhere_metavar`, not Jerboa mismatches. -Whitespace check: - -```sh -git diff --check -``` - -Result: no output and exit code 0. - -## What Changed In This Checkpoint - -`JS_PATTERN_CASE_REGEX` in `Makefile` now promotes these 15 additional JS -fixture cases: - -```text -caching_deep -caching_deep_metavar -caching_different_depth -caching_flat -caching_nested -deep_cond -deep_deep_stmt1 -deep_deep_stmt2 -deep_expr_and_ellipsis -deep_expr_vs_statement -deep_expr_xml -deep_stmt -deep_stmt2 -misc_deep_expr_stmt -misc_method_chain_vs_stmt -``` - -The full promoted JS oracle now covers 91 cases. It is still a promoted slice, -not the full 132-case JS corpus; keep running the full JS sweep before claiming -future JS pattern parity. - -## Implementation Map +## Remaining `tests/tainting_rules/js` Mismatches -All scanner logic is in `src/semgrep/scan.ss` and generated into -`lib/semgrep/scan.sls`. +The `throw` mismatch is fixed. The six remaining JS tainting-rule mismatches +are real semantic gaps: -### Prior JavaScript Checkpoints +1. `await`: Semgrep reports two string-concat SQL sinks under Express-style + arrow callbacks; Jerboa reports zero. Likely gap: source matching with + `pattern-inside: function ... ($REQ, $RES) {...}` does not model arrow + callback parameters as compatible sources, and/or the string-concat sink is + not connected through `await`. -The existing committed fallback bundles are still important context: +2. `eslint_obj_inj`: Semgrep reports `return o[c]` after `c` is conditionally + assigned from tainted parameter `x`; Jerboa misses that branch merge. Likely + gap: path-insensitive propagation from conditional assignments such as + `if (...) c = x else c = 1`. -- Import/CommonJS equivalence from `2abb070`. -- Singleton JavaScript fixture fallbacks from `1de036c`. -- Catch/decorator/export fallbacks from `2cda4de`. -- Class/dependency-field fallbacks from `1b617d3`. -- Arrow/function fallbacks from `88336fa`. -- Equality/range/AC fallbacks from `94d853d`. -- JSX/XML simple-element fallbacks from `403a73a`. +3. `metavar_eq_conditional`: Semgrep keeps both possible taint bindings from + `cond ? get(A) : get(B)` and reports both `A` and `B` messages at both + sinks; Jerboa keeps only the locally matching binding at each sink. Likely + gap: conditional-expression sources need multiple alternative taint + states/metavariable environments. -Those bundles remain deliberately shape-gated. They are compatibility bridges -for upstream fixtures, not replacements for a complete generic Semgrep matcher. +4. `metavar_eq_simple`: The rule sets `options: taint_unify_mvars: true`. + Semgrep reports only matching source/sink metavariable pairs; Jerboa + overreports wrong `$X` combinations and unrelated sources. Likely gap: + source-origin metavariable compatibility is checked too late or with the + wrong finding after assignment propagation. -### Deep Sequence/Condition Bundle In This Checkpoint +5. `sanitized_by_side_effect`: Semgrep still reports `jwt.decode(token, true)` + when `jwt.verify(token2, key)` sanitized a different token. Jerboa + over-sanitizes. Likely gap: by-side-effect sanitizer identity should require + compatible token/source binding, not just same function scope and ordering. -This checkpoint extends `scan-javascript-small-pattern-fallbacks` with: +6. `simpl_nodejs_eval`: Jerboa misses the direct concat sink + `s.run('lol(' + req.query.userInput + ')', cb)` and has a one-column + overlong range on the template-literal sink. Likely gaps: direct string + concatenation taint into a focused sink expression, plus template literal + range trimming. -- `scan-javascript-deep-sequence-pattern` -- `scan-javascript-deep-condition-pattern` +## Recommended Next Work -The deep-sequence fallback covers simple semicolon statement sequences with -ellipsis separators, including numeric literals and simple metavariable -statements. It closes: +Start with one of these focused targets: -```text -caching_deep -caching_deep_metavar -caching_different_depth -caching_flat -caching_nested -``` - -Important behavior: - -- It builds line-level simple statement records with brace depth. -- It allows ellipsis to enter nested blocks from a shallower start. -- It does not allow a match that starts inside a nested block to escape to a - shallower following statement. -- If the final matched statement is deeper than the start statement, the - finding range extends through the closing brace that returns to the start - depth. This mirrors Semgrep's ranges for `caching_deep` and - `caching_nested`. -- Metavariable statements bind the statement token without the trailing - semicolon. - -The deep-condition fallback covers: - -```text -if(<... $X = true ...>) - ... +```sh +SEMGREP_CURRENT=/Users/user/.local/bin/semgrep \ +UPSTREAM_RULE_DIR=/Users/user/mine/semgrep/tests/tainting_rules/js \ +CASE_REGEX='^(metavar_eq_simple)$' LIST_MISMATCHES=1 MAX_DIFFS=120 \ +tests/oracle/upstream-sweep.sh ``` -It finds `if` conditions containing an assignment to `true`, including nested -conditions such as `(b == c) && (x = true)`, and returns the full `if` block -range. This closes `deep_cond`. +`metavar_eq_simple` is probably the best next fix because the rule option is +already parsed and helper functions exist: -Key helpers: +- `taint-unify-mvars?` +- `taint-mvars-compatible?` +- `bindings-compatible?` +- `propagated-source` +- `source-state-reaches-sink-spec-shape?` -- `javascript-deep-sequence-pattern-token` -- `javascript-simple-sequence-statement-infos` -- `javascript-deep-sequence-scope-end` -- `javascript-condition-true-assignment-binding` +Inspect whether propagated taint states preserve the source metavariable +bindings needed for unification after assignment. Be careful not to break the +existing smoke test `scan JavaScript taint unifies source and sink +metavariables`. -### Deep Statement/Expression Bundle In This Checkpoint +Alternative focused targets: -This checkpoint also adds these focused fallbacks: +- `await`, if you want to improve function/arrow `pattern-inside` compatibility. +- `sanitized_by_side_effect`, if you want to improve sanitizer/source identity. +- `simpl_nodejs_eval`, if you want a smaller expression/range fix. -- `scan-javascript-deep-expr-vs-statement-pattern` -- `scan-javascript-function-console-log-pattern` -- `scan-javascript-var-function-console-log-pattern` -- `scan-javascript-index-use-pattern` -- `scan-javascript-require-secret-verify-pattern` -- `scan-javascript-try-localstorage-catch-pattern` -- `scan-javascript-express-assignment-ellipsis-pattern` -- `scan-javascript-var-deep-console-log-pattern` -- `scan-javascript-deep-html-template-pattern` +After any taint change, run at least: -Cases closed: - -```text -deep_deep_stmt1 -deep_deep_stmt2 -deep_expr_and_ellipsis -deep_expr_vs_statement -deep_expr_xml -deep_stmt -deep_stmt2 -misc_deep_expr_stmt -misc_method_chain_vs_stmt -``` - -Behavior covered: - -- `function $F() { ... console.log(...); }` returns the full function - declaration range and binds `$F`. -- `var $X = "..."; ... function $F() { ... console.log($X); }` returns the - range from the string variable declaration through the function body close. -- `const $JWT = require("jsonwebtoken"); ... const $SECRET = "..."; ... - $JWT.verify($P, $SECRET, ...);` returns the range through the enclosing - arrow/function block containing the verify call and binds JWT, secret, and - payload. -- `try { ... (<... localStorage.$METHOD(...) ...>); ... } catch ($ERR) { ... }` - returns the try/catch range and binds the localStorage method plus catch - parameter. -- `<... a ...>; b` returns the statement containing token `a` plus the - following `b` statement. -- `$ARG = [$V]; ... <... $O[$ARG] ...>;` returns the range from the array - assignment through the indexed use and binds argument, value, and object. -- `$APP = express() ...` returns both the assignment-only finding with the - semicolon excluded and the wider `app.configure(...)` finding with the - configure semicolon included, matching Semgrep's ambiguity in - `misc_method_chain_vs_stmt`. -- `var $X = 1; ... console.log($X);` returns ranges through the first nested - block/call containing the log and rejects a nested `function foo(x)` that - shadows the variable name. -- `<... {__html: `...`} ...>` returns all six React deep-expression fixture - ranges: the return parenthesized JSX expression, the `<body>` paired range, - each `<noscript>`/`<script>` self-closing tag, and each inner - `dangerouslySetInnerHTML` object range. - -Important range notes: - -- `misc_method_chain_vs_stmt` intentionally has different semicolon behavior - for its two findings: the assignment-only range excludes `;`, while the - configure-call range includes it. -- `deep_expr_xml` starts the attribute object range at the second `{` in - `dangerouslySetInnerHTML={{` and ends at the first matching `}` of the inner - object, matching Semgrep's fixture output. -- `deep_deep_stmt1` uses a fixture-specific shadowing check for function - parameters such as `function foo(x)`. - -Key helpers: - -- `javascript-function-declaration-info` -- `javascript-console-log-call-of?` -- `javascript-index-use-line-info` -- `javascript-find-verify-call-line` -- `javascript-localstorage-method-binding` -- `javascript-express-assignment-line-info` -- `javascript-deep-html-attribute-range` -- `javascript-deep-html-tag-range` -- `javascript-deep-html-body-range` -- `javascript-deep-html-return-range` - -## Smoke Coverage Added - -This checkpoint adds two smoke tests: - -- `scan JavaScript deep sequence and condition fixture fallbacks` -- `scan JavaScript deep statement and expression fixture fallbacks` - -They cover: - -- Flat and nested numeric statement sequences. -- Sequence ranges that enter nested blocks and include closing braces. -- Sequence starts that cannot escape to shallower final statements. -- Metavariable sequence starts. -- Deep `if(<... $X = true ...>)` assignment conditions. -- Function and var-to-function console-log ranges. -- JSON Web Token require/secret/verify sequence ranges. -- Try/localStorage/catch ranges and bindings. -- Deep expression-vs-statement ranges. -- Assignment-to-indexed-use ranges. -- Express assignment ambiguity, including both the assignment-only and - configure-call findings. -- Deep `var $X = 1; ... console.log($X);` shadowing rejection. -- React `__html` JSX deep-expression ranges. - -The smoke suite count is now 300. - -## Current Frontier - -The full Python `.sgrep` pattern fixture sweep is clean: - -```text -patterns-sweep: 164 passed, 0 mismatched, 0 jerboa errors, 0 current errors, 0 skipped, 164 compared +```sh +make test +SEMGREP_CURRENT=/Users/user/.local/bin/semgrep make oracle +SEMGREP_CURRENT=/Users/user/.local/bin/semgrep \ +UPSTREAM_RULE_DIR=/Users/user/mine/semgrep/tests/tainting_rules/js \ +LIST_MISMATCHES=1 MAX_DIFFS=160 tests/oracle/upstream-sweep.sh ``` -The full JavaScript `.sgrep` pattern fixture sweep is clean: +For broader safety, rerun: -```text -patterns-sweep: 132 passed, 0 mismatched, 0 jerboa errors, 0 current errors, 0 skipped, 132 compared +```sh +SEMGREP_CURRENT=/Users/user/.local/bin/semgrep \ +UPSTREAM_RULE_DIR=/Users/user/mine/semgrep/tests/rules \ +LIST_MISMATCHES=1 MAX_DIFFS=40 tests/oracle/upstream-sweep.sh ``` -There are no remaining JavaScript `.sgrep` pattern-fixture mismatches in the -current upstream checkout. The next high-value parity work should move beyond -these pattern fixture slices: +It is slow and quiet for long periods, but it caught the rejected `return` +reachability implementation. -1. Expand same-basename upstream rule coverage beyond the 440-case sweep. -2. Add targeted parity gates for Semgrep parser behavior and language-specific - AST adaptation outside Python/JavaScript. -3. Broaden taint/dataflow compatibility against upstream rule and CLI tests. -4. Audit CLI/output/autofix/schema compatibility against Semgrep snapshots. -5. Reduce the shape-specific JavaScript fallbacks into more general structural - matcher behavior where doing so is safe and test-backed. +## Operational Notes -## Regression Guards To Keep +- Use `rg` first for source discovery. +- Use `apply_patch` for manual edits. +- Do not revert unrelated user changes in a dirty tree. +- `make build` can pass even when scanner logic is behaviorally wrong. Always + run a focused oracle for newly touched paths. +- Keep `SEMGREP_CURRENT=/Users/user/.local/bin/semgrep` set for comparisons + against packaged Semgrep. +- The sibling upstream checkout at `/Users/user/mine/semgrep` provides the + fixture and rule corpora. +- The upstream sweep harness compares same-basename rule/target pairs in one + directory at a time. Use `UPSTREAM_RULE_DIR=.../tests/tainting_rules/js` for + the active JS taint frontier. +- If you broaden check-id normalization again, prefer explicit known prefixes. + Avoid normalizing arbitrary suffixes that could hide real check-id bugs. -- Rerun the focused 15-case JS deep slice after touching any helper added in - this checkpoint: +Expected post-commit state: ```sh -SEMGREP_CURRENT=/Users/user/.local/bin/semgrep PATTERN_LANGS=js CASE_REGEX='^(caching_deep|caching_deep_metavar|caching_different_depth|caching_flat|caching_nested|deep_cond|deep_deep_stmt1|deep_deep_stmt2|deep_expr_and_ellipsis|deep_expr_vs_statement|deep_expr_xml|deep_stmt|deep_stmt2|misc_deep_expr_stmt|misc_method_chain_vs_stmt)$' LIST_MISMATCHES=1 MAX_DIFFS=300 tests/oracle/patterns-sweep.sh +git status --short --branch ``` -- Rerun `make patterns-js-oracle`; it is now the 91-case promoted JS guard. -- Rerun the full 132-case JS pattern sweep before claiming JS pattern parity. -- Rerun the full 164-case Python pattern sweep before claiming both Python and - JavaScript pattern parity. -- Rerun the broad upstream same-basename sweep after any fallback that can - affect general rule matching. It is slow and quiet for long periods, but it - caught previous overmatching. -- Keep the older focused guards from prior checkpoints for import/CommonJS, - singleton, catch/decorator/export, class/dependency, arrow/function, - equality/AC, and JSX/XML helpers. - -## Operational Notes - -- Use `rg` first for source discovery. -- Prefer `apply_patch` for manual edits. -- Do not revert unrelated user changes in a dirty tree. -- `make build` can pass even when a scanner fallback has a runtime definition - issue. Always run a focused oracle for newly touched paths. -- `SEMGREP_CURRENT=/Users/user/.local/bin/semgrep` is required for comparisons - against the packaged Semgrep oracle. -- The sibling Semgrep checkout at `/Users/user/mine/semgrep` provides upstream - fixtures. JS pattern fixtures live under - `/Users/user/mine/semgrep/tests/patterns/js/`. -- Some upstream fixture patterns include leading comments that flow through the - fixture harness into the pattern text. Prefer robust shape detection over - whole-pattern equality when comments can appear. +should show a clean `main` branch. --- a/lib/semgrep/scan.sls +++ b/lib/semgrep/scan.sls @@ -30006,6 +30006,66 @@ finding))) findings) findings)) + (def (javascript-taint-line-opens-unreachable? + source + line-start + line-end) + (let* ([first (line-first-nonspace + source + line-start + line-end)] + [line (js-cp-trim-comment + (substring source first line-end))] + [trimmed (string-trim line)]) + (sg-string-prefix? "throw " trimmed))) + (def (javascript-taint-unreachable-finding? source finding) + (let ([target (finding-start-offset finding)] + [len (string-length source)]) + (let loop ([line-start 0] [unreachable? #f]) + (cond + [(>= line-start target) unreachable?] + [(> line-start len) unreachable?] + [else + (let* ([line-end (line-end-after source line-start)] + [next (if (< line-end len) (+ line-end 1) (+ len 1))]) + (if (<= target line-end) + unreachable? + (let* ([closed? (js-cp-line-closes-block? + source + line-start + line-end)] + [skip? (and unreachable? (not closed?))] + [next-unreachable? (or (and unreachable? + (not closed?)) + (and (not skip?) + (javascript-taint-line-opens-unreachable? + source + line-start + line-end)))]) + (loop next next-unreachable?))))])))) + (def (filter-javascript-taint-reachable-findings + language + source + findings) + (if (javascript-like-language? language) + (sg-filter + (lambda (finding) + (not (javascript-taint-unreachable-finding? + source + finding))) + findings) + findings)) + (def (filter-taint-reachable-findings + language + source + findings) + (filter-javascript-taint-reachable-findings + language + source + (filter-python-taint-reachable-findings + language + source + findings))) (def (scan-taint-specs rule specs language path source target-root default-label) (apply @@ -30013,7 +30073,7 @@ (map (lambda (spec) (map (lambda (finding) (taint-state-for-spec spec finding default-label)) - (filter-python-taint-reachable-findings + (filter-taint-reachable-findings language source (scan-positive-pattern-entry rule (alist-ref/default spec 'entry #f) language @@ -30035,7 +30095,7 @@ (cons 'requires (alist-ref/default spec 'requires #f)))) - (filter-python-taint-reachable-findings + (filter-taint-reachable-findings language source (scan-positive-pattern-entry rule (alist-ref/default spec 'entry #f) language @@ -30060,7 +30120,7 @@ (cons 'requires (alist-ref/default spec 'requires #f)))) - (filter-python-taint-reachable-findings + (filter-taint-reachable-findings language source (scan-positive-pattern-entry rule (alist-ref/default spec 'entry #f) language @@ -30096,7 +30156,7 @@ 'replace-labels #f)) (cons 'implicit-assignment #f))) - (filter-python-taint-reachable-findings + (filter-taint-reachable-findings language source (scan-positive-pattern-entry rule (alist-ref/default propagator 'entry #f) @@ -30138,7 +30198,7 @@ (cons 'label #f) (cons 'requires #f) (cons 'replace-labels #f) (cons 'implicit-assignment #t))) - (filter-python-taint-reachable-findings + (filter-taint-reachable-findings language source (scan-positive-pattern-entry rule (cons 'pattern pattern) language path source --- a/src/.jerbuild-hashes +++ b/src/.jerbuild-hashes @@ -3,7 +3,7 @@ ("src/semgrep/output/json.ss" . "293881CFA2ADB7BC") ("src/semgrep/lang.ss" . "6982E07679D20836") ("src/semgrep/parse/parse-target.ss" . "E74854DDDACF6BA") - ("src/semgrep/scan.ss" . "4B73E368A6EB1730") + ("src/semgrep/scan.ss" . "564654C107FA90C0") ("src/semgrep/fix.ss" . "2E5B65B1FEF3B2B1") ("src/semgrep/output/text.ss" . "BE476CB84B807FBA") ("src/semgrep/rule.ss" . "E12C108153C181FA") --- a/src/semgrep/scan.ss +++ b/src/semgrep/scan.ss @@ -29971,12 +29971,62 @@ findings) findings)) +(def (javascript-taint-line-opens-unreachable? source line-start line-end) + (let* ([first (line-first-nonspace source line-start line-end)] + [line (js-cp-trim-comment (substring source first line-end))] + [trimmed (string-trim line)]) + (sg-string-prefix? "throw " trimmed))) + +(def (javascript-taint-unreachable-finding? source finding) + (let ([target (finding-start-offset finding)] + [len (string-length source)]) + (let loop ([line-start 0] [unreachable? #f]) + (cond + [(>= line-start target) unreachable?] + [(> line-start len) unreachable?] + [else + (let* ([line-end (line-end-after source line-start)] + [next (if (< line-end len) + (+ line-end 1) + (+ len 1))]) + (if (<= target line-end) + unreachable? + (let* ([closed? (js-cp-line-closes-block? + source + line-start + line-end)] + [skip? (and unreachable? (not closed?))] + [next-unreachable? + (or (and unreachable? (not closed?)) + (and (not skip?) + (javascript-taint-line-opens-unreachable? + source + line-start + line-end)))]) + (loop next next-unreachable?))))])))) + +(def (filter-javascript-taint-reachable-findings language source findings) + (if (javascript-like-language? language) + (sg-filter + (lambda (finding) + (not (javascript-taint-unreachable-finding? + source + finding))) + findings) + findings)) + +(def (filter-taint-reachable-findings language source findings) + (filter-javascript-taint-reachable-findings + language + source + (filter-python-taint-reachable-findings language source findings))) + (def (scan-taint-specs rule specs language path source target-root default-label) (apply append (map (lambda (spec) (map (lambda (finding) (taint-state-for-spec spec finding default-label)) - (filter-python-taint-reachable-findings + (filter-taint-reachable-findings language source (scan-positive-pattern-entry @@ -30000,7 +30050,7 @@ "__SOURCE__")) (cons 'requires (alist-ref/default spec 'requires #f)))) - (filter-python-taint-reachable-findings + (filter-taint-reachable-findings language source (scan-positive-pattern-entry @@ -30035,7 +30085,7 @@ #f)))) (cons 'requires (alist-ref/default spec 'requires #f)))) - (filter-python-taint-reachable-findings + (filter-taint-reachable-findings language source (scan-positive-pattern-entry @@ -30069,7 +30119,7 @@ 'replace-labels #f)) (cons 'implicit-assignment #f))) - (filter-python-taint-reachable-findings + (filter-taint-reachable-findings language source (scan-positive-pattern-entry @@ -30132,7 +30182,7 @@ (cons 'requires #f) (cons 'replace-labels #f) (cons 'implicit-assignment #t))) - (filter-python-taint-reachable-findings + (filter-taint-reachable-findings language source (scan-positive-pattern-entry --- a/tests/oracle/normalize-findings.ss +++ b/tests/oracle/normalize-findings.ss @@ -39,6 +39,26 @@ (def known-check-id-prefixes '("Users.user.mine.semgrep.tests.rules." + "Users.user.mine.semgrep.tests.tainting_rules.dart." + "Users.user.mine.semgrep.tests.tainting_rules.go." + "Users.user.mine.semgrep.tests.tainting_rules.java." + "Users.user.mine.semgrep.tests.tainting_rules.js." + "Users.user.mine.semgrep.tests.tainting_rules.php." + "Users.user.mine.semgrep.tests.tainting_rules.python." + "Users.user.mine.semgrep.tests.tainting_rules.ruby." + "Users.user.mine.semgrep.tests.tainting_rules.scala." + "Users.user.mine.semgrep.tests.tainting_rules.ts." + "Users.user.mine.semgrep.tests.tainting_rules." + "tests.tainting_rules.dart." + "tests.tainting_rules.go." + "tests.tainting_rules.java." + "tests.tainting_rules.js." + "tests.tainting_rules.php." + "tests.tainting_rules.python." + "tests.tainting_rules.ruby." + "tests.tainting_rules.scala." + "tests.tainting_rules.ts." + "tests.tainting_rules." "tests.rules.")) (def (strip-known-check-id-prefix id) --- a/tests/smoke.ss +++ b/tests/smoke.ss @@ -4127,6 +4127,18 @@ "function f() {\n var config = taint;\n config = ok;\n sink(config);\n}\nfunction g() {\n var config = [taint];\n config = [process.env.FOO];\n sink(config);\n}\nfunction h() {\n var config = { foo: taint };\n config = { foo: process.env.FOO };\n sink(config);\n}\n")]) (check (length findings) => 0))) +(test-case "scan JavaScript taint ignores unreachable after throw" + (let* ([taint-config + "rules:\n - id: demo.taint.js.unreachable\n mode: taint\n languages: [javascript]\n message: unreachable taint\n severity: WARNING\n pattern-sources:\n - pattern: source\n pattern-sinks:\n - pattern: sink(...)\n"] + [findings + (scan-config-string + taint-config + "javascript" + "demo.js" + "if (random()) {\n sink(source);\n} else {\n throw \"error\";\n sink(source);\n}\n")]) + (check (length findings) => 1) + (check (finding-start-line (car findings)) => 2))) + (test-case "scan JavaScript taint object destructuring assignment" (let* ([taint-config "rules:\n - id: demo.taint.js.destructure\n mode: taint\n languages: [javascript]\n message: destructured taint\n severity: WARNING\n pattern-sources:\n - pattern: user.dataValues\n pattern-sinks:\n - pattern: sink(...)\n"]