mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-18 04:29:27 +00:00
feat(benchmark): Phase 2 (task 4) — actionable per-rule signals, ledger, FP knob
Complete Phase 2: make benchmark output drive per-rule improvement, wire the
loop-owned ledger, and turn a confirmed false positive into a score knob. Zero
new dependencies; detection stays byte-identical (recall 44/47 = 93.6%, FP 0%)
and the 90/10 gate holds.
- Per-rule attribution (4.1): BenchmarkMiss/BenchmarkFalsePositive carry rule_id;
BenchmarkMetrics.per_rule aggregates matched/missed/false_positives per rule.
- Recommendation delta (4.2): evaluate_benchmark emits RuleRecommendation
(keep/shadow/loosen) written to rule_policy_delta.json; the inert
next_improvement_candidate stub is now rule-aware ("rule:<id>" or
"language_hunter").
- Loop ledger (4.3): cli loads the ruleset with the rule_policy.json overlay
(empty -> byte-identical; an entry overlays a rule's status/floors at load);
read_rule_ledger/write_rule_ledger added for the evolve loop. A disabled rule
in the ledger no longer fires (covered by an integration test).
- FP -> reachability knob (4.4): a confirmed false-positive learning lowers a
finding's effective reachability multiplier in the score
(fp_reachability_overrides + scoring reachability_override) instead of deleting
the rule, so recall stays high while the score stops over-penalizing.
93 tests pass; ruff + mypy clean. Spec tasks 4.1-4.4 checked off — Phase 2
(Core: rules-as-data + benchmark signals) is complete. The loop-driven decision
to apply ledger flips remains the gated evolve loop (Phase 3/4).
Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
444d8c9490
commit
2d7ef48e52
@@ -249,6 +249,30 @@ def learnings_from_verifications(
|
||||
return learnings
|
||||
|
||||
|
||||
# A confirmed false positive lowers a finding's effective reachability multiplier
|
||||
# by this factor in the project score (it does not delete the rule).
|
||||
FP_REACHABILITY_FACTOR = 0.4
|
||||
_BASE_REACH_MULT = {"reachable": 1.0, "inferred-file-surface": 0.6, "unknown": 0.4}
|
||||
|
||||
|
||||
def fp_reachability_overrides(
|
||||
findings: list[StaticFinding],
|
||||
learnings: list[FalsePositiveLearning],
|
||||
) -> dict[str, float]:
|
||||
"""Lowered score multipliers for findings whose scope has a confirmed FP.
|
||||
|
||||
Keyed by ``finding_id``; consumed by the scoring model so a confirmed false
|
||||
positive reduces the score penalty instead of the rule being deleted.
|
||||
"""
|
||||
overrides: dict[str, float] = {}
|
||||
for finding in findings:
|
||||
vulnerability_class = _vulnerability_class(finding)
|
||||
if any(_learning_applies(finding.path, learning) and learning.vulnerability_class == vulnerability_class for learning in learnings):
|
||||
base = _BASE_REACH_MULT.get(finding.reachability_status, _BASE_REACH_MULT["unknown"])
|
||||
overrides[finding.finding_id] = round(base * FP_REACHABILITY_FACTOR, 3)
|
||||
return overrides
|
||||
|
||||
|
||||
def _reason_from_verification(finding: StaticFinding, status: VerificationStatus) -> FalsePositiveReason:
|
||||
if finding.reachability_status == "unknown":
|
||||
return FalsePositiveReason.UNREACHABLE_PATH
|
||||
|
||||
Reference in New Issue
Block a user