feat(corpus-seeded-mechanisms): reports name the mechanism, its known fix guard and pair provenance

Task 5.1 (Req 6.1, 6.3).

- reports.py: the markdown and SARIF writers take the mechanisms a run
  cites; a finding cites one through its mechanism:<id> tag (variant) or
  its merged overlay record's mechanism_id; markdown shows Mechanism,
  Known fix guard and Learned from pairs per finding plus a Mechanisms
  section, SARIF properties carry mechanism_id/summary/guard/pairs;
  variant findings keep suspicion, merged inventory findings keep their
  own evidence level; reports without mechanisms are unchanged.
- cli: _search_variants returns the cited mechanisms from the store and
  the scan passes them to both writers.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01T7XD7x5KonMdmGRkp63EQG
This commit is contained in:
Marius Ciepluch
2026-09-05 15:35:08 +02:00
co-authored by Claude Fable 5.1
parent 23aae79768
commit 624024e72a
4 changed files with 225 additions and 9 deletions
+21 -6
View File
@@ -405,6 +405,7 @@ def _run_scan(path: Path, config_path: Path, mode: str, fail_on: str) -> ScanOut
wrote_overlay = False
variants_payload: dict[str, object] | None = None
variant_findings: list[StaticFinding] = []
mechanisms_cited: dict[str, dict[str, object]] = {}
if Stage.MAP in plan.requested:
built_map = runtime.run_stage(
"map",
@@ -438,7 +439,7 @@ def _run_scan(path: Path, config_path: Path, mode: str, fail_on: str) -> ScanOut
findings = findings + extra
write_findings(findings, findings_path)
if config.variants.enabled:
variant_findings, overlay_records, variants_payload = _search_variants(
variant_findings, overlay_records, variants_payload, mechanisms_cited = _search_variants(
root=run.target,
targets=targets,
overlay_records=overlay_records,
@@ -545,6 +546,7 @@ def _run_scan(path: Path, config_path: Path, mode: str, fail_on: str) -> ScanOut
complexity_map=built_map,
verdicts=verdict_records if wrote_verdicts else None,
overlay=overlay_records if wrote_overlay else None,
mechanisms=mechanisms_cited or None,
),
)
runtime.run_stage(
@@ -554,6 +556,7 @@ def _run_scan(path: Path, config_path: Path, mode: str, fail_on: str) -> ScanOut
verifications,
sarif_path,
overlay=overlay_records if wrote_overlay else None,
mechanisms=mechanisms_cited or None,
),
)
runtime.run_stage(
@@ -865,19 +868,23 @@ def _search_variants(
facts: SemanticFacts | FactLoadError,
max_mechanisms: int,
runtime: HarnessRuntime,
) -> tuple[list[StaticFinding], list[OverlayRecord], dict[str, object]]:
"""Third MAP proposer (corpus-seeded-mechanisms Req 3): shapes from the mechanism store, merged with overlay flows."""
) -> tuple[list[StaticFinding], list[OverlayRecord], dict[str, object], dict[str, dict[str, object]]]:
"""Third MAP proposer (corpus-seeded-mechanisms Req 3): shapes from the mechanism store, merged with overlay flows.
Also returns the mechanisms the findings cite (summary, guard, pairs, cwe) for the reports (Req 6.1).
"""
from .semantic.mechanisms import corpus_mechanisms
from .semantic.variant_search import hits_to_findings, search_tree
store = MechanismStore(root / CALIBRATION_DIR / "mechanisms.jsonl")
empty: dict[str, object] = {"mechanisms_searched": 0, "files_searched": 0, "findings": 0, "merged_into_overlay": 0}
if isinstance(facts, FactLoadError):
return [], overlay_records, empty
return [], overlay_records, empty, {}
result = runtime.run_stage("variants", lambda: search_tree(root, targets, store, facts, max_mechanisms=max_mechanisms))
for degradation in result.degradations:
runtime.state["degradations"].append(degradation)
summaries = {record.id: (record.summary, record.pairs, record.cwe) for record in corpus_mechanisms(store.load())}
records_by_id = {record.id: record for record in corpus_mechanisms(store.load())}
summaries = {record.id: (record.summary, record.pairs, record.cwe) for record in records_by_id.values()}
findings, records = hits_to_findings(result.hits, overlay_records, summaries)
merged = sum(1 for before, after in zip(overlay_records, records, strict=True) if before.mechanism_id is None and after.mechanism_id)
payload: dict[str, object] = {
@@ -886,7 +893,15 @@ def _search_variants(
"findings": len(findings),
"merged_into_overlay": merged,
}
return findings, records, payload
cited_ids = {tag.split(":", 1)[1] for finding in findings for tag in finding.tags if tag.startswith("mechanism:")} | {
str(record.mechanism_id) for record in records if record.mechanism_id
}
cited: dict[str, dict[str, object]] = {
mechanism_id: {"summary": record.summary, "guard": record.guard, "pairs": list(record.pairs), "cwe": record.cwe}
for mechanism_id, record in records_by_id.items()
if mechanism_id in cited_ids
}
return findings, records, payload, cited
def _hotspot_from_variant(finding: StaticFinding) -> Hotspot:
+57 -3
View File
@@ -1,7 +1,7 @@
from __future__ import annotations
import json
from collections.abc import Sequence
from collections.abc import Mapping, Sequence
from dataclasses import asdict
from pathlib import Path
@@ -19,10 +19,12 @@ def write_markdown_report(
complexity_map: object | None = None,
verdicts: Sequence[object] | None = None,
overlay: Sequence[object] | None = None,
mechanisms: Mapping[str, Mapping[str, object]] | None = None,
) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
verification_by_id = _verification_by_id(verifications or [])
overlay_by_id = _overlay_by_id(overlay or [])
cited: dict[str, Mapping[str, object]] = {}
lines = ["# OpenUltraSAST Report", "", f"Findings: {len(findings)}", "", "## Inventory", ""]
if not findings:
lines.append("No quick-mode findings were emitted.")
@@ -51,7 +53,16 @@ def write_markdown_report(
if disposition is not None:
lines.append(f"- Disposition: `{getattr(disposition, 'disposition', '')}`")
lines.append(f"- Overlay reason: `{getattr(disposition, 'reason', '')}`")
mechanism_id = _mechanism_id_for(finding, disposition)
if mechanism_id and mechanisms is not None and mechanism_id in mechanisms:
info = mechanisms[mechanism_id]
cited[mechanism_id] = info
lines.append(f"- Mechanism: `{mechanism_id}` — {info.get('summary', '')}")
lines.append(f"- Known fix guard: `{info.get('guard', 'none')}`")
lines.append(f"- Learned from pairs: `{', '.join(_pairs_of(info))}`")
lines.extend(["", finding.rationale, ""])
if cited:
_append_mechanisms(lines, cited)
if overlay:
_append_overlay(lines, overlay)
if complexity_map is not None:
@@ -133,6 +144,7 @@ def write_sarif_report(
verifications: list[VerificationResult],
path: Path,
overlay: Sequence[object] | None = None,
mechanisms: Mapping[str, Mapping[str, object]] | None = None,
) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
verification_by_id = _verification_by_id(verifications)
@@ -151,7 +163,9 @@ def write_sarif_report(
}
},
"results": [
_sarif_result(finding, verification_by_id.get(finding.finding_id), overlay_by_id.get(finding.finding_id))
_sarif_result(
finding, verification_by_id.get(finding.finding_id), overlay_by_id.get(finding.finding_id), mechanisms=mechanisms
)
for finding in findings
],
}
@@ -249,7 +263,13 @@ def _sarif_rules(findings: list[StaticFinding]) -> dict[str, dict[str, object]]:
return rules
def _sarif_result(finding: StaticFinding, verification: VerificationResult | None, overlay: object | None = None) -> dict[str, object]:
def _sarif_result(
finding: StaticFinding,
verification: VerificationResult | None,
overlay: object | None = None,
*,
mechanisms: Mapping[str, Mapping[str, object]] | None = None,
) -> dict[str, object]:
properties: dict[str, object] = {
"finding_id": finding.finding_id,
"severity": finding.severity,
@@ -263,6 +283,14 @@ def _sarif_result(finding: StaticFinding, verification: VerificationResult | Non
if overlay is not None:
properties["disposition"] = getattr(overlay, "disposition", "")
properties["overlay_reason"] = getattr(overlay, "reason", "")
mechanism_id = _mechanism_id_for(finding, overlay)
if mechanism_id:
properties["mechanism_id"] = mechanism_id
info = (mechanisms or {}).get(mechanism_id)
if info is not None:
properties["mechanism_summary"] = str(info.get("summary", ""))
properties["mechanism_guard"] = str(info.get("guard", "none"))
properties["mechanism_pairs"] = _pairs_of(info)
return {
"ruleId": _rule_id(finding),
"level": _sarif_level(finding.severity),
@@ -354,3 +382,29 @@ def _relative_artifact(root: Path, path: Path) -> str:
return path.relative_to(root).as_posix()
except ValueError:
return path.as_posix()
def _mechanism_id_for(finding: StaticFinding, overlay: object | None) -> str | None:
"""Mechanism cited by a finding: a `mechanism:<id>` tag on a variant finding, or the merged overlay record's id."""
for tag in finding.tags:
if tag.startswith("mechanism:"):
return tag.split(":", 1)[1]
merged = getattr(overlay, "mechanism_id", None)
return str(merged) if merged else None
def _append_mechanisms(lines: list[str], cited: Mapping[str, Mapping[str, object]]) -> None:
lines.extend(
["## Mechanisms", "", "Known mechanisms this report cites (corpus-seeded; variant findings stay suspicions until verified).", ""]
)
for mechanism_id, info in sorted(cited.items()):
pairs = ", ".join(_pairs_of(info)) or "unknown"
lines.append(
f"- `{mechanism_id}`: {info.get('summary', '')}; known fix guard `{info.get('guard', 'none')}`; learned from `{pairs}`"
)
lines.append("")
def _pairs_of(info: Mapping[str, object]) -> list[str]:
pairs = info.get("pairs")
return [str(p) for p in pairs] if isinstance(pairs, list | tuple) else []