fix(recon): label cross-language sinks in white-box evidence (Closes #165) (#166)
* test(recon): RED — non-Python sinks are attack_surface with no evidence label DANGEROUS_SINK_RE flags Go/Java/JS/C sinks but SINK_EVIDENCE_RES is Python-only, so classify() returns attack_surface with an empty sink: evidence set for every non-Python language. Pins the invariant that an attack_surface-by-sink block always carries a sink: label. Fails on main for 8 cross-language sinks. Refs #165 * feat(recon): label cross-language sinks in evidence, matching classification SINK_EVIDENCE_RES was Python-only while DANGEROUS_SINK_RE is cross-language, so a Go/Java/JS/C block classified attack_surface with no sink: evidence — a blank reason in operator triage and the reasoning-layer context pack, and (via prioritize's +10*riskSignals.length) under-ranked vs a Python peer. Add the 11 missing branches to the evidence table using the IDENTICAL sub-regexes from DANGEROUS_SINK_RE (exec.Command, Runtime.getRuntime, ProcessBuilder, bare system/popen/exec-family, http.Get/Post/NewRequest, http.request, client.Do/Get/Post, fetch, axios), establishing the invariant that an attack_surface-by-sink block always carries a sink: label. Exposure classification is untouched (DANGEROUS_SINK_RE unchanged); the only behavioral change is priority — a non-Python sink now scores +10 for its now- present label, the correct direction. Full suite green incl. the priority- ordering and Python-control regressions, so the Python RED-baseline is unmoved. Closes #165 * fix(recon): de-duplicate overlapping sink evidence per-occurrence A cross-language body matching a specific sink whose text is a superset of a generic pattern (popen(⊃open(; Runtime.getRuntime().exec(⊃exec()) pushed two sink: signals for one call, doubling its priority weight (+10*length). Suppress the generic label only when EVERY generic occurrence in the body is covered by a specific one (equal match counts) — so a genuinely separate generic call in the same body (a real open(path) beside a popen(cmd), or an engine.exec(code) beside Runtime.getRuntime().exec(cmd)) still reports. Pre-existing Python overlaps (subprocess.Popen, urllib…urlopen -> open()) are left unchanged; altering Python priority is out of scope here. * fix(recon): make Runtime.getRuntime->exec() subsumption adjacency-based Adversarial review found the per-occurrence subsumption unsound for the Runtime.getRuntime/exec() pair: unlike popen(⊃open( (textually nested), the two patterns match independent text, so equal *bare* counts is coincidence, not coverage. In pure Java 'Runtime.getRuntime().gc(); other.exec(cmd)' has one of each -> the real other.exec(cmd) command sink was silently suppressed, losing its label and its +10 priority weight (my earlier residual claim that this needs cross-language mixing was wrong). Count only the exec( that actually chains off getRuntime() via a per-row 'covered' regex; a detached .exec( is never covered, so it still reports. The nested popen/open pair is unchanged (covered = the popen pattern itself). Also: sinkOccurrences preserves the source regex flags (latent desync trap if a future evidence pattern adds a flag); document the module-load throw as an intentional fail-fast on internal constants. Tests: equal-count detached-exec regression (the case the first cut missed), idiom-collapse single-signal, and negative guards for the (?<![\w.]) bare-call guard and the [Cc]lient. receiver guard.
L
Lyubomir Bozhinov committed
9674f8e904663f68f4de1436944455e36b5c5a02
Parent: 2803b58
Committed by GitHub <noreply@github.com>
on 9/3/2026, 2:38:14 AM