Fix two silent consistency-override security false-negatives (Stage-1 + Stage-2) - #195
Merged
Merged
Conversation
…ngrading VULNERABLE->SAFE At Stage 1 there is no per-finding exploit evidence, only pattern similarity (the weakest signal). run_stage1_consistency_check applied verdict overrides unconditionally, so a same-signature-pattern sibling that is SAFE could overwrite a real VULNERABLE finding -> the scan reports clean (silent security false-negative). Block SURFACED(VULNERABLE/BYPASSABLE)->SAFEISH(SAFE/PROTECTED) downgrades and record the rejected suggestion for audit; upgrades and lateral moves are unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ely-exploitable finding to safe _has_conclusive_exploit_path only protects conclusively-BROKEN paths, so a conclusively-EXPLOITABLE finding (sink reached, attacker control, path unbroken) could be overwritten to a sibling's 'safe' verdict by the consistency resolver. Add the mirror predicate _has_conclusive_exploitable_path (defaults require proof) and a direction-aware guard that blocks a downgrade-to-safe of such a finding. Existing broken-path guard untouched; a legitimate vulnerable->bypassable refinement still applies. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… (Swift-relevant) Stage-1 groups by basename, so same-filename findings in different directories group. This regression test asserts the downgrade-block + audit breadcrumb on that vector - the interaction a concurrent Swift-parser PR would exercise (mechanism is language-agnostic). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
gadievron
requested review from
dgeyshis,
shahar-davidson and
sounil
as code owners
July 26, 2026 20:21
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fix two silent consistency-override security false-negatives (Stage-1 + Stage-2)
Branch:
pr-2a-consistency-override-fixes(3 commits offmaster/d4caf8a) · Target:knostic/OpenAnt:masterDiff: 4 files, +204 (2 fixes +44, 3 guard tests +160) · Not yet pushed — awaiting approval.
Problem
Both stages of the pipeline can silently downgrade a real vulnerable finding to
safevia pattern-consistency, so the scan reports clean and exits 0.utilities/stage1_consistency.py):run_stage1_consistency_checkapplies LLM-proposed verdict overrides unconditionally (result["verdict"] = new_verdict). A same-signature-pattern sibling that isSAFEcan overwrite a realVULNERABLEfinding. At Stage 1 there is no per-finding exploit/verification evidence — only pattern similarity, the weakest signal in the pipeline.utilities/finding_verifier.py): the override guard_has_conclusive_exploit_pathprotects only conclusively-broken paths. A conclusively-exploitable finding (sink reached, attacker control full/partial, path unbroken) is not protected → it can be overwritten to a sibling'ssafeverdict. One-sided guard.Both are silent false-negatives — the cardinal sin for a SAST tool.
Fix (directional, purely subtractive)
Make consistency-override directional: upgrades and lateral moves flow freely; only a downgrade-to-safe with weaker-than-existing evidence is blocked.
VULNERABLE/BYPASSABLE → SAFE/PROTECTEDdowngrades; recordstage1_consistency_downgrade_blockedfor audit._has_conclusive_exploitable_path(defaults require proof — a missing field never reads as exploitable) + a direction-aware guard blocking a downgrade-to-safe of a conclusively-exploitable finding; recordconsistency_downgrade_blocked. The existing broken-path guard is untouched, and a legitimatevulnerable→bypassablerefinement still applies.Purely subtractive: it only declines to apply a proposed downgrade — it never synthesizes an upgrade (no group-inflation false positives).
Testing
tests/test_stage1_consistency_no_downgrade.py(4, incl. the cross-dir coupling test),tests/test_verifier_consistency_no_exploitable_downgrade.py(5) — 9 pass on thisd4caf8abase. On the pre-fix code the 6 downgrade-block assertions FAIL (verifier 5/5, stage1 1/3); the other 2 stage1 tests assert unchanged behaviors (grouping, upgrade) and correctly pass on base — the standard RED/GREEN split for regression guards.Why it's safe (blast radius)
normalize_resultskeeps all dict keys; no strict schema).Prior art / provenance
openant-kb) as grade-A facts, re-derived at the KB pinc81c0e1(Stage-1stage1_consistency.py:241unguarded override; Stage-2 one-sided_has_conclusive_exploit_path). Not in the KB's already-fixed / Do-Not-Reintroduce set.d4caf8a(this PR's base) in identical form — verified.Reviewer notes
test_same_basename_cross_directory_findings_group_and_downgrade_is_blocked. Not a blocker.python -c "import json,sys; [print(r.get('route_key'), r.get('stage1_consistency_downgrade_blocked') or r.get('consistency_downgrade_blocked')) for r in json.load(open(sys.argv[1]))['results'] if 'stage1_consistency_downgrade_blocked' in r or 'consistency_downgrade_blocked' in r]" results.json(or
jq '.results[] | select(.stage1_consistency_downgrade_blocked or .consistency_downgrade_blocked) | {route_key, stage1_consistency_downgrade_blocked, consistency_downgrade_blocked}' results.json).Rollback
Git-revert-clean: the two commits touch only
stage1_consistency.py+finding_verifier.py(+ their tests),disjoint from every other in-flight change. Reverting restores the prior behavior exactly — with the caveat
that a revert re-opens the two silent
vulnerable→safefalse-negatives (that is the behavior being fixed).Note the fix trades toward retaining a false positive over dropping a true positive; if a downstream consumer
mis-handles the new
*_downgrade_blockedaudit keys, revert is safe and immediate.