Refine argument-taint roadmap: origin-guard works, propagator 1->0 is next bug
ober
a2a8aa260593bb873e76de8e72fd977288cb5826
--- a/HANDOFF_OPUS_4_8.md +++ b/HANDOFF_OPUS_4_8.md @@ -225,16 +225,44 @@ languages, so closing them is the tip of the larger work: test5) which `field-source-taints-base-text?` won't anchor. THEN (3c) value-analysis (`.length`→numeric test4, `.compareTo`→numeric test10, string-concat test7). - ATTEMPTED + REVERTED (2026-06-02): a syntactic `source-token-in-sink-arg?` - (bare-identifier source appears as a token in the sink's argument) handled - `sink(wrap(x))` but is TOO SYNTACTIC — it ignores labels/requires, sanitizers, - and by-side-effect ordering, so it broke 7 smoke taint tests (the metavar - branch matched a `source($X)` argument; a propagated label-source's NAME `foo` - matched unrelated sinks; by-side-effect typestate sources reached every call). - LESSON: argument-taint must flow through the propagation graph — the call's - RESULT becomes a derived taint-state that respects labels/sanitizers — not a - text token match. A real subproject, deferred. The RECEIVER fix (3a) is - safe precisely because it is anchored on the source variable as the + ATTEMPTED + REVERTED TWICE (2026-06-02): a token-occurrence reach + (`source-token-in-sink-arg?`: source variable appears as a token in the sink's + argument / propagator RHS). + - v1 (purely syntactic) broke 7 smoke taint tests. + - v2 added the right discriminator — `original-bare-variable-source?` = + `(equal? origin finding)` (NOT propagated) AND `simple-identifier-text?` + (a bare variable) AND `(not side-effect?)`. This FIXED the labeled-propagator + case (a propagated label-source has origin/=finding, so its name `foo` no + longer spoofs unrelated sinks) and most others, and made `bad` + (`sink("a"+x)`) + numbers1 test7 reach. But TWO smoke cases still leak: + (i) `source($X)` produces a source-state whose finding is the metavar value + `user_input` (origin==finding, bare id) so `sink(user_input)` false-matches + — need to exclude metavar-bound sources (try `(null? (finding-metavars ...))`, + but verify the focus-param source survives it); (ii) by-side-effect typestate + creates a DERIVED state on `l` that isn't flagged side-effect, so `lock(l)` + re-reaches. Also, once args reach, booleans need their own value-analysis + (ok2/ok3 `Boolean.valueOf(x)` reach but aren't suppressed) — same chicken/egg + as numbers test8/9. + - v3 (cleanest so far) SPLIT sink vs propagator. The SINK-only reach + (`source-token-in-sink-arg?` in the two sink gates, origin-guarded) is + SMOKE-CLEAN (321/321) — the v2 "leaks" (i)/(ii) were RED HERRINGS from raw + `grep` trace-line noise; the real smoke breakers were all on the PROPAGATOR + path. Sink-only alone, handler-off: booleans1 now catches `bad` (18) but adds + FPs at ok2/ok3 (10/14, need boolean value-analysis); numbers1 still misses 40 + (test7 is a propagator chain, not a sink arg). The PROPAGATOR clause + (`original-bare-variable-source?` token in the assignment RHS, in + `source-taints-binding?`) is what makes test7 reach AND completes numbers1 + (test8/9 already suppressed by the numeric value-analysis 3c) — BUT it breaks + ONE smoke test, `Java query string taint with text fallback`, paradoxically + 1→0 (adding an OR clause REDUCES findings: the legit `queryParams.get`→id→ + query path disappears). That 1→0 is the real bug to chase next — likely a + self-taint / label-set or dedupe interaction when the propagated `id` also + token-matches its own RHS. NEXT: debug that 1→0 (scheme harness replicating + the smoke case is at /tmp/jqh.ss pattern); once fixed, sink+propagator + completes numbers1 handler-off. THEN boolean value-analysis for booleans1. + LESSON: the origin/side-effect/bare-id guard is the right discriminator; + sink-side is landable now, propagator-side needs the 1→0 fix. The RECEIVER fix + (3a) is safe precisely because it is anchored on the source variable as the expression's base, which a derived/label/arg occurrence can't spoof. The `test` rule-id group needs (2)+(3a/b/c) + best-fit-sink, getter/setter, lambda, jpa, FQN before its overfit clause retires. The same engine retires the