# Effective Severity Selection Last updated: 2025-11-25 (Docs Tasks Md.V) ## Goal Provide a deterministic, explainable way to pick the *effective* severity for a vulnerability or VEX observation when multiple signals exist (CVSS, KEV, exploit intel, VEX status, asset criticality, policy overrides). ## Inputs - **Base scores**: CVSS v3/v4 (base + temporal) and ecosystem-native severities (npm/yarn, PyPI, Maven). Missing scores are treated as `null`. - **Exploitability**: KEV flag, EPSS probability, in-the-wild sightings, vendor exploit flags. - **VEX status**: `not_affected`, `affected`, `fixed`, `under_investigation`, `unknown` (per OpenVEX/CSAF/CycloneDX-VEX). - **Context signals**: asset criticality, exposure surface (`internet`, `intranet`, `airgap`), runtime enablement flag, workload type. - **Policy overrides**: tenant/org rules (allow lists, waiver ids, force-upgrade requirements, SLA class). ## Algorithm (deterministic) 1) **Normalize** all scores to a 0–10 float with 3-decimal rounding; store provenance for each signal. 2) **VEX gate** - `not_affected` → effective severity `None` (score 0). Short-circuit unless override `force_evaluate=true`. - `fixed` → keep score but add mitigation note. 3) **Exploit boost**: if `KEV=true` or `EPSS >= 0.7`, set `exploit_boost = +1.0` (cap at 10). Record reason. 4) **Exposure clamp** - `airgap` → max score 7.0 unless override `allow_airgap_breakglass`. - `intranet` → no cap; `internet` → no cap. 5) **Criticality weight**: multiply by asset criticality weight (default 1.0; high=1.2, medium=1.0, low=0.8). Clamp 0–10. 6) **Policy override**: apply explicit tenant rules (force severity, waive to `None`, or constrain max). Overrides always log the applied rule id. 7) **Bucket** into severity bands (stable mapping): | Score range (inclusive) | Band | | --- | --- | | 9.0–10.0 | Critical | | 7.0–8.9 | High | | 4.0–6.9 | Medium | | 0.1–3.9 | Low | | 0 | None | All arithmetic uses `decimal` and rounds only when persisted or returned (3 decimals) to stay replayable. ## Examples - CVSS 7.5 + KEV + internet + high criticality → base 7.5 → +1.0 exploit → *before clamp* 8.5 → ×1.2 = 10.2 → clamp 10.0 → **Critical**. - CVSS 5.0, `not_affected` VEX → short-circuit to **None** (score 0) with rationale `vex:not_affected`. - No CVSS, EPSS 0.2, exposure `airgap` → default score 0, band **None**; remains deterministic. ## Observability - Emit `stellaops.policy.effective_severity` histogram (0–10) with tags `tenant`, `source`, `vex_status`, `kev`, `epss_bucket`, `criticality`, `override_id`. - Log structured event `severity.selected` containing input signals, applied steps, final score/band. - Traces: span attribute `severity.score` and `severity.band`; link to upstream ingest span (`traceparent` propagated). ## Determinism & Offline posture - No live network lookups during evaluation; KEV/EPSS/VEX feeds must be preloaded from frozen bundles. - Sorting of tied severities: break ties by subject id (lexicographic) then source priority (`vex` > `kev` > `cvss` > `ecosystem`). - All timestamps are UTC ISO-8601; caches keyed by `(tenant, subject, advisory)`. ## Contract for consumers - API and CLI MUST return both the raw inputs and the chosen band/score so auditors can replay decisions. - Downstream UIs should surface the rationale chain (steps 2–6) and any overrides applied. - Waivers must reference the override id that changed the severity.