feat: control-driven SAST — LUT + grounded LLM triage over tool findings #213

Merged
sharang merged 7 commits from feat/grounded-control-check into main 2026-07-21 09:01:52 +00:00
7 Commits
Author SHA1 Message Date
Sharang ParnerkarandClaude Fable 5 753ecefaae fix(semgrep): extract CWE from list-form metadata (was always null)
CI / Check (push) Skipped
CI / Check (pull_request) Successful in 5m54s
CI / Detect Changes (pull_request) Skipped
CI / Deploy Agent (pull_request) Skipped
CI / Deploy Dashboard (pull_request) Skipped
CI / Deploy Docs (pull_request) Skipped
CI / Deploy MCP (pull_request) Skipped
semgrep emits metadata.cwe as a LIST (e.g. "CWE-798: Use of Hard-coded
Credentials"), but the extractor called v.as_str() on it -> always None, so SAST
findings never carried a CWE at all (silently breaking control mapping AND the
CWE-based dedup). Handle list + bare-string forms and normalise to the CWE-NNN
id. Found by the live control-triage end-to-end test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 10:55:00 +02:00
Sharang ParnerkarandClaude Fable 5 075a4cb81b feat(agent): wire control triage into the scan pipeline (end-to-end SAST)
CI / Check (pull_request) Successful in 5m59s
CI / Detect Changes (pull_request) Skipped
CI / Deploy Agent (pull_request) Skipped
CI / Deploy Dashboard (pull_request) Skipped
CI / Deploy Docs (pull_request) Skipped
CI / Deploy MCP (pull_request) Skipped
CI / Check (push) Skipped
After the deterministic tools run, the orchestrator's new control_triage stage
stamps each finding with the compliance control(s) it is evidence for and flags
control false positives. triage_repo_findings builds control specs from the
ingested OSCAL catalog, reads a code window per finding, and runs ControlTriage
(control-map LUT -> grounded judge). Adds Finding.control_refs (serde default);
the ground gate stamps it. Opt-in via BREAKPILOT_BASE_URL. 2 region tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 10:28:01 +02:00
Sharang ParnerkarandClaude Fable 5 f712ba1e60 feat(agent): control triage — LLM false-positive filter over tool findings
ControlTriage composes the pipeline: a deterministic tool finding ->
controls_for(tool,cwe) [control-map LUT] -> grounded judge confirms/refutes ->
TriageOutcome { Unmapped | Confirmed([control ids]) | FalsePositive }. The LLM
enters ONLY here, as an FP filter over tool output (ZeroFalse/IRIS), never as the
detector; only judgments grounded to real code survive. Reuses the judge + core
ground gate + control-map. 3 lib tests (confirm/refute/unmapped).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 10:20:31 +02:00
Sharang ParnerkarandClaude Fable 5 b7534d1123 feat(control-map): deterministic control->scan LUT crate (CRA, 40 controls)
New standalone crate — the 'transcribing' layer that maps each control to the
static-scan step(s) that check it, or marks it needs_tooling / not_code_checkable.
Authored + human-reviewed: no LLM decides coverage. The LLM only triages the
tool's findings downstream (in the agent), never here.

- ControlMap / ControlEntry / ScanBinding / Coverage types + embedded JSON LUT
- query API: coverage(control), controls_for(tool, cwe), summary()
- CRA LUT: 40 controls -> 9 covered (semgrep/gitleaks/syft/osv) / 16 needs_tooling
  / 15 not_code_checkable
- wired into CI (clippy + test). 4 lib tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 10:15:26 +02:00
Sharang ParnerkarandClaude Opus 4.8 d1f42a1a83 feat(agent): grounded control checker — compose judge -> ground gate
GroundedControlChecker<J: ControlJudge>::check(spec, regions, repo_id) judges each
candidate region and keeps only findings that survive the core grounding gate.
Generic over the judge so tests drive it with a deterministic stub — the
recognize->ground path is proven without an LLM (grounded snippet kept, ungrounded
dropped, non-violation yields nothing). 2 lib tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 22:51:57 +02:00
Sharang ParnerkarandClaude Opus 4.8 b0117078f3 feat(agent): control judge — LLM recognize stage (temp 0, verbatim, fail-closed)
ControlJudge trait + LlmControlJudge: judges one (control, region) with a closed
temperature-0 prompt that must quote the offending code VERBATIM; parsing fails
closed to non-violation (never a fabricated finding). Behind a trait so the
checker stays stub-testable. Its output is re-checked by the core grounding gate,
never trusted directly. 3 lib tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 22:50:20 +02:00
Sharang ParnerkarandClaude Opus 4.8 bfeb3f041a feat(core): grounded control-check backbone (ground gate + cache)
The deterministic spine of control-driven checking: turn a text control into a
finding via an LLM used as a grounded pattern-recognizer. ground() admits a
verdict only if its quoted snippet appears verbatim in the retrieved region and
recomputes the finding line from that match (the model's line is discarded), so
a fabricated snippet cannot survive. cache_key() makes verdicts reproducible.
Pure — no LLM, no IO. 4 lib tests incl. fabricated-snippet-dropped.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 22:47:10 +02:00