Documentation
¶
Overview ¶
Package scoring ports src/sec_af/scoring.py — the deterministic exploitability scoring engine: the severity/evidence/reachability weight tables, the CWE severity floors, and the score → label mapping.
Everything here is pure: no LLM, no I/O. The orchestrator calls ComputeExploitabilityScore for every VerifiedFinding in _generate_output and ApplyCWESeverityFloor before that.
Index ¶
- Variables
- func ApplyCWESeverityFloor(cweID string, current schemas.Severity) schemas.Severity
- func AssignSeverityLabel(score float64) string
- func ComputeExploitabilityScore(finding schemas.VerifiedFinding) float64
- func ComputePriorityRank(findings []schemas.VerifiedFinding) []schemas.VerifiedFinding
Constants ¶
This section is empty.
Variables ¶
var CWESeverityFloor = map[string]schemas.Severity{ "CWE-78": schemas.SeverityCritical, "CWE-77": schemas.SeverityCritical, "CWE-94": schemas.SeverityCritical, "CWE-95": schemas.SeverityCritical, "CWE-96": schemas.SeverityCritical, "CWE-89": schemas.SeverityCritical, "CWE-502": schemas.SeverityCritical, "CWE-918": schemas.SeverityHigh, "CWE-287": schemas.SeverityHigh, "CWE-290": schemas.SeverityHigh, "CWE-306": schemas.SeverityHigh, "CWE-798": schemas.SeverityHigh, "CWE-22": schemas.SeverityHigh, "CWE-611": schemas.SeverityHigh, "CWE-79": schemas.SeverityMedium, "CWE-840": schemas.SeverityHigh, "CWE-862": schemas.SeverityHigh, "CWE-863": schemas.SeverityHigh, }
CWESeverityFloor is the minimum severity for well-known vulnerability classes. LLMs consistently underrate injection and RCE, so this is a hard floor: CWE-78 can never be reported as "medium".
Ports scoring.py CWE_SEVERITY_FLOOR — byte-for-byte the same 18 entries.
var EvidenceMultipliers = map[schemas.EvidenceLevel]float64{ schemas.EvidenceLevelFullExploit: 1.0, schemas.EvidenceLevelExploitScenarioValidated: 0.9, schemas.EvidenceLevelSanitizationBypassable: 0.7, schemas.EvidenceLevelReachabilityConfirmed: 0.5, schemas.EvidenceLevelFlowIdentified: 0.3, schemas.EvidenceLevelStaticMatch: 0.1, }
EvidenceMultipliers scales the base weight by how strong the proof is.
Ports scoring.py EVIDENCE_MULTIPLIERS (declared there in decreasing order; the map has no order, the values are what matter).
var ReachabilityMultipliers = map[string]float64{
"externally_reachable": 1.0,
"internally_reachable": 0.7,
"requires_auth": 0.5,
"requires_admin": 0.3,
}
ReachabilityMultipliers scales the score by how exposed the code path is.
Ports scoring.py REACHABILITY_MULTIPLIERS.
var SeverityWeights = map[schemas.Severity]float64{ schemas.SeverityCritical: 10.0, schemas.SeverityHigh: 8.0, schemas.SeverityMedium: 5.0, schemas.SeverityLow: 3.0, schemas.SeverityInfo: 1.0, }
SeverityWeights is the base weight per severity label.
Ports scoring.py SEVERITY_WEIGHTS. Keyed by the ENUM VALUE (Python indexes it with `finding.severity.value`), which is what schemas.Severity already is.
Functions ¶
func ApplyCWESeverityFloor ¶
ApplyCWESeverityFloor upgrades a severity when the CWE has a known minimum floor, and otherwise returns it unchanged.
Ports scoring.py apply_cwe_severity_floor. Python parity: the comparison is `_SEVERITY_ORDER.get(label, 0) > _SEVERITY_ORDER.get(current.value, 0)`, so an UNKNOWN current severity ranks as 0 (info) and any floor beats it.
func AssignSeverityLabel ¶
AssignSeverityLabel maps an exploitability score onto a severity label.
Ports scoring.py assign_severity_label. Boundaries are inclusive on the lower end: >=9 critical, >=7 high, >=4 medium, >=1 low, else info.
func ComputeExploitabilityScore ¶
func ComputeExploitabilityScore(finding schemas.VerifiedFinding) float64
ComputeExploitabilityScore is the 0-10 exploitability score for a finding: severity weight × evidence multiplier × reachability multiplier × chain bonus, clamped to [0, 10] and rounded to 2 decimals.
Ports scoring.py compute_exploitability_score. The chain bonus is 2.0 when the finding belongs to an attack chain, else 1.0.
Python parity divergence (deliberate, documented): Python indexes SEVERITY_WEIGHTS / EVIDENCE_MULTIPLIERS directly, so an out-of-vocabulary severity or evidence level raises KeyError. Pydantic makes that unreachable in Python (both fields are enums), but Go's schemas.Severity is a string type that a hand-built struct could set to anything. Go treats an unknown key as weight 0.0 — the score collapses to 0.0 rather than panicking mid audit.
The final `round(..., 2)` goes through pyfmt.Round, which reproduces CPython's round-half-to-EVEN on the exact binary value — NOT math.Round(x*100)/100, which rounds half away from zero and accumulates the scaling error.
func ComputePriorityRank ¶
func ComputePriorityRank(findings []schemas.VerifiedFinding) []schemas.VerifiedFinding
ComputePriorityRank returns the findings sorted by exploitability score, highest first, as a NEW slice (Python's `sorted()` copies; the input order is untouched).
Ports scoring.py compute_priority_rank. Python's sorted() is stable and `reverse=True` preserves the original order among equal keys, so Go uses sort.SliceStable — sort.Slice would be free to reorder ties and break tests/test_scoring.py::test_compute_priority_rank_is_stable_for_equal_scores. The score is computed ONCE per finding (Python's `key=` does the same), which also keeps the comparator cheap.
Types ¶
This section is empty.