SIGN IN SIGN UP

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