Read first: Scoring developer guide — mental model, CI flags, JSON fields.
This page is the technical v2 reference (formulas and implementation map).
Status: GA (default --scoring both)
ADR: adr-003-scoring-v2.md
Legacy spec: scoring-spec.md
SARIF: sarif-score-v2.md
v2 adds score_v2 with absolute risk (integer, higher = worse) next to legacy score.overall (0–100, higher = better).
Excluded from v2 sum: compliance, attack_chains meta-findings. Tool-attributed findings from other analyzers are scored.
bracket = 1 + Σ factor_increments
base_risk = severity_w × bracket
finding_risk = round(base_risk × chain_factor)
absolute_risk = Σ finding_risk
Factor increments come from classifiers in weights_v1.yaml under classifiers:. Evidence tags on findings refine classifiers when emitters populate reachability_tag, exploitability_class, etc.
chain_factor applies to tool findings on validated graph paths (hop_count ≥ 1). Severity floor: medium+. Meta chain rows are display-only.
| hop_count | chain_factor |
|---|---|
| 0–1 | 1.0 |
| 2 | 1.15 |
| 3 | 1.35 |
| 4+ | 1.50 |
| Field | Description |
|---|---|
absolute_risk |
Stable integer sum |
security_score |
100 - percentile(absolute_risk, corpus) when stats available |
risk_level |
Band from corpus or literals: low/medium/high/critical |
risk_range |
Confidence interval on absolute risk (not driven by finding confidence) |
dimension_scores |
Eight factor axes 0–100 (higher = worse) |
top_contributors |
Top 10 findings/paths by contribution |
category_scores_v2 |
Separate OWASP tiles, 100 = good (dashboard JSON) |
basis |
Scorable counts, excluded meta-rows, weights_hash |
Confidence affects confidence_score and risk_range only — never absolute_risk. Inputs are v2-scorable findings with aligned per-finding risks:
pairs = [(risk, finding) for finding, risk in zip(scorable, risks) if risk > 0]
if no pairs → confidence_score = 100
else confidence_score = round(100 × Σ(effective_confidence(f) × risk) / Σ risk)
effective_confidence applies per-analyzer caps from uncertainty.py when finding.confidence >= 0.99.
if absolute_risk == 0 → risk_range = (0, 0), label = "high"
mean_conf = weighted mean of effective_confidence by finding_risk
base_spread = absolute_risk × (1 - mean_conf) × 0.35
spread = base_spread × evidence_quality_factor × analyzer_disagreement_factor
low = max(0, round(absolute_risk - spread))
high = round(absolute_risk + spread)
label = high if mean_conf >= 0.85 else medium if mean_conf >= 0.65 else low
evidence_quality_factor: 0.8 when any of:- Aggregated
risk_tagsincludes bothlive_probeandhandler_traced, or - A finding has
finding_type=validatedandruntime_validationin{live_probe, live_proxy}, or - A validated taint finding (
runtime_validation=taint_param_sink) includes a handlersnippetinevidence.facts - Otherwise 1.2 (Phase 3 wire-up in
uncertainty.py+runtime_evidence.py)
- Aggregated
analyzer_disagreement_factor: 1.4 when conflicting severities share a tool; else 1.0
- Rank scorable findings by
finding_riskdescending; take up to 9 rows (type=finding). - Append one explainability row (
type=attack_chain) for the highesthop_countpath when paths exist and total rows < 10. - JSON export caps at 10 rows and omits verbose
evidence_tags.
Per-finding contributor fields: risk_contribution, confidence (effective × 100), chain_factor, factors breakdown.
Per-axis raw sum = Σ factor increment for that axis across scorable findings. Normalized relative to this scan (0–100; highest-loaded axis = 100):
if raw <= 0 → 0
else → min(100, round(100 × raw / max(raw across all axes on this scan)))
This shapes the factor radar (which axes dominate on the current server). Corpus-wide benchmarking uses absolute_risk and security_score, not per-axis tiles.
Packaged dimension_p95 in corpus stats is retained for calibration scripts but is not used for dimension_scores display.
| Flag | Applies to |
|---|---|
--min-score |
Legacy only |
--min-security-score |
v2 benchmark score |
--max-absolute-risk |
v2 absolute risk |
--max-risk-level |
v2 band |
--min-category-score-v2 |
v2 OWASP tiles (100=good; fail when below minimum) |
--fail-on-category |
Legacy category tiles only |
| Module | Role |
|---|---|
scoring/engine_v2.py |
Sum, verify, contributors |
scoring/context.py |
build_scoring_context, chain factors |
scoring/graph.py |
canonical_attack_graph, build_paths |
scoring/evidence_tags.py |
PR-4b analyzer evidence tag helpers |
scoring/evidence_emit.py |
Graph/scope-dependent evidence enrichment |
scoring/weights_v1.yaml |
Classifier lookup tables |