fix(cpg): single-query path drops params the script does not declare

taint.request_params emits guards/fixedOrigin/originAnchors/quotedSanitizers/
fieldParameterSources, which taint.sc reads from per-request JSON in its BATCH
branch but does NOT declare on `@main def exec` and does NOT consume in its
single-request branch (it defaults them). The single-query path passed them as
`--param guards=...` (disposable) or named args (session), so joern errored
`Unknown arguments` and every cpg.run("taint", ...) failed -- a silent zero of
the shape AGENTS.md warns about. scan_region->taint_verdict->_usable_flows and
the pair-path verdict()/verdicts() were dead; the push/scan batch route was
unaffected (it carries the extras as JSON the script parses itself).

Fix: parse the script's `@main def exec(...)` signature once (_declared_exec_params
+ memoised _declared_params) and, in query(), filter params to the declared set
before building either route. Faithful, not lossy -- the single-request branch
could never use those 5 fields. No-op for config/dominance (their request_params
emit only declared params; verified). Only taint sheds the 5 extras.

Verified end-to-end on banks (patched backend in the replay image): single-query
path now runs; baseline 0 findings, annotated search 2 path/model_entailed
findings (prompt->write_text). Regression test asserts no undeclared param
reaches the command line and the query parses, with a fake runner (no Joern).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
Marius Ciepluch
2026-10-12 14:16:36 +02:00
co-authored by Claude Opus 4.8
parent 8f7fc4ae83
commit a024656da0
4 changed files with 160 additions and 1 deletions
@@ -0,0 +1,22 @@
PATCH PRESENT: True
=== building banks CPG (python) ===
CPG built.
shipped path spec: sources=22 sinks=('FileResponse', 'open', 'os.remove', 'os.unlink', 'send_file', 'send_from_directory', 'shutil.copy', 'shutil.move', 'shutil.rmtree')
ANNOTATED path spec sinks: ('FileResponse', 'open', 'os.remove', 'os.unlink', 'read_text', 'send_file', 'send_from_directory', 'shutil.copy', 'shutil.move', 'shutil.rmtree', 'write_text')
=== BASELINE (shipped search: no source annotation, no sink annotation) ===
[baseline] from_prompt_path (parameter_sources=False): 0 finding(s)
[baseline] set (parameter_sources=False): 0 finding(s)
[baseline] _load (parameter_sources=False): 0 finding(s)
[baseline] _scan (parameter_sources=False): 0 finding(s)
=== FIXED SEARCH (LLM annotation injected: param source + write_text/read_text sink) ===
[fixed] from_prompt_path (parameter_sources=True): 1 finding(s)
family=path rung=model_entailed site=src/banks/registries/directory.py:45:from_prompt_path witness=path -> prompt_file.write_text(prompt.raw) in src/banks/registries/directory.py (line 45,
[fixed] set (parameter_sources=True): 0 finding(s)
[fixed] _load (parameter_sources=True): 0 finding(s)
[fixed] _scan (parameter_sources=True): 1 finding(s)
family=path rung=model_entailed site=src/banks/registries/directory.py:155:_scan witness=self -> self._index_path.write_text(self._index.model_dump_json()) in src/banks/registries
=== RESULT: baseline path findings=0 fixed-search path findings=2 ===
FIXED SEARCH CATCHES BANKS