Skip to content

feat(adjudication): show the judge only reachable (loaded-at-runtime) CVEs (JEF-453)#252

Merged
thejefflarson merged 1 commit into
mainfrom
thejefflarson/skip-nonreachable-cves
Jul 20, 2026
Merged

feat(adjudication): show the judge only reachable (loaded-at-runtime) CVEs (JEF-453)#252
thejefflarson merged 1 commit into
mainfrom
thejefflarson/skip-nonreachable-cves

Conversation

@thejefflarson

Copy link
Copy Markdown
Owner

Closes JEF-453. The source-level fix for the recurring false exploitable — the judge fabricating a loaded-at-runtime tag onto a not-observed CVE.

The change

The judge's CVE field now carries only [reachability: loaded-at-runtime] CVEs — the sole CVE category that is exploitation evidence. not-observed / present-static-binary / unknown-reachability CVEs are context ("how bad IF exploited"), kept on the dashboard for operators but omitted from the judge prompt. If none are reachable → (none).

  • Filter lives in prompt.rs::render_evidence (prompt layer only). The anti-fabrication guards (guard_fabricated_cve, guard_fabricated_reachability_tag) keep their full-evidence view via model_call, so their behaviour is unchanged.
  • Removing the not-observed CVE removes the fabrication target (audit R1: loaded-at-runtime is the most-primed phrase; the judge no longer sees a not-observed CVE to relabel). The two now-moot "trap" instructions are deleted, shrinking the prompt.

Why this is ADR-0029-compliant

ADR-0029's evidence-capping prohibition protects the reachable-objectives breadth (breadth is part of the judgement; hiding objectives could change a correct verdict). A not-observed CVE is different: by the prompt's own definition it is context, never evidence, so omitting it from the judge cannot hide a breach. This is "don't feed the judge non-evidence," not "cap the evidence" — the objective list is still shown in full. Recorded as an ADR-0029 scope-note.

Validation (deployed qwen3:1.7b, A/B old-vs-new)

case OLD NEW
protector flip @ temp 0.8 (boundary mass) 3/20 = 15% 0/17 = 0%
log4j (loaded-at-runtime CVE) exploitable 5/5 exploitable
live-signal (Falco alert) exploitable exploitable
exposed-secret 5/5 exploitable breach 5/5 (4 exploitable + 1 confirmed)

The false-positive boundary mass collapses 15% → 0% with zero false negatives. The one wobble (exposed-secret occasionally confirmed not exploitable) is breach-positive and harmless in shadow.

Also

  • Bakeoff resynced to the new engine prompt (the audit flagged SYS drift): the all-not-observed fixtures' CVE field → (none), and a --temp boundary-mass A/B mode added (the sensitive metric for a rare tail). Resynced protector case refutes 3/3.
  • Prompt-content tests updated: the shared critical_cve fixture is now loaded-at-runtime so it survives the filter; the not-observed / static-binary tests now assert omission from the judge prompt.
  • gitignore /scratchpad/ and untrack an audit report that leaked in via a git add -A.
  • Full suite + clippy + fmt green.

Ships as v0.3.107, after v0.3.105 (JEF-445 unfreeze) and v0.3.106 (JEF-451 guard) — the prompt fix, its deterministic backstop, and the unfreeze together close this false-positive class.

🤖 Generated with Claude Code

… CVEs (JEF-453)

The recurring false `exploitable` (JEF-451) is the judge fabricating a
`[reachability: loaded-at-runtime]` tag onto a CVE that is actually not-observed.
The fable audit's root cause R1: `loaded-at-runtime` is the most-primed phrase in
the prompt (~10x in instructions, 0x in evidence), and the prompt showed the judge
not-observed CVEs — a non-evidence target to relabel.

Fix (evidence-shaping, prompt layer): the judge's CVE field carries ONLY
`loaded-at-runtime` CVEs — the sole CVE category that is exploitation evidence.
not-observed / present-static-binary / unknown-reachability CVEs are context ("how
bad IF exploited"), kept on the dashboard for operators but omitted from the judge
prompt (filter in `render_evidence`; the anti-fabrication guards keep their full
view, so their behaviour is unchanged). If none are reachable → "(none)". This lets
the two now-moot "trap" instructions be deleted, shrinking the prompt.

This removes NON-evidence, not the objective-breadth capping ADR-0029 forbids (a
not-observed CVE can never change a correct verdict). Recorded as an ADR-0029
scope-note.

Validated on the deployed qwen3:1.7b, A/B old-vs-new:
- temp-0.8 boundary mass on the protector flip prompt: OLD 3/20 (15%) -> NEW 0/17 (0%).
- no false negatives: log4j (loaded-at-runtime) 5/5 exploitable; live-signal
  exploitable; exposed-secret breach 5/5 (4 exploitable + 1 confirmed, both
  breach-positive).

Also:
- Bakeoff resynced: SYS mirrors the new engine prompt; the all-not-observed
  fixtures' CVE field -> "(none)"; added a `--temp` boundary-mass A/B mode.
- Prompt-content tests updated (critical_cve fixture is now loaded-at-runtime so it
  survives the filter; the not-observed/static-binary tests assert OMISSION).
- gitignore /scratchpad/ and untrack an audit report that leaked in via git add -A.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VtjoJttCvBY4dzCoE4f9vP
@thejefflarson
thejefflarson merged commit feba2b6 into main Jul 20, 2026
5 checks passed
@thejefflarson
thejefflarson deleted the thejefflarson/skip-nonreachable-cves branch July 20, 2026 04:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant