| name | cve-asset-triage |
| description | Standard operating procedure for correlating a single CVE to the specific assets it affects and computing the blast radius across the estate. Use when a CVE lands and the question is not just "how bad is it" but "which of MY hosts are actually affected, how exposed are they, and what could an attacker reach next". Combines nvd_lookup + epss_kev exploit signals with asset_lookup inventory correlation, keeps all scoring deterministic, emits the CVETriage shape, and requires human sign-off before any patch or ticket action. |
CVE-to-Asset Triage SOP
How a security operations team turns a raw CVE identifier into a concrete, per-asset affected list plus a blast-radius map. Where the cve-triage-rubric skill answers "how severe is this CVE in the abstract", this SOP answers the operational follow-on: which assets in this estate are affected, are they reachable, and what sits downstream of them. The output is deterministic and defensible — the same CVE + same inventory always yields the same affected list.
Operating Principles (read first)
- Correlate, do not assume. Affected-asset status comes from
asset_lookup inventory (installed component / version / known_vuln flag), never from guessing which hosts "probably" run the package.
- Deterministic math only. Severity tiers, exposure classification, and blast-radius counts follow fixed rules. Missing data is recorded as
UNKNOWN, never interpolated.
- Assume prod when uncertain. An asset whose criticality or component presence cannot be confirmed is treated as production and component-present until proven otherwise.
- Cite every claim. Each affected-asset judgement traces to a tool result (
nvd_lookup / epss_kev / asset_lookup) or is UNKNOWN. No confabulation of hostnames, versions, or CVSS numbers.
- Human-gated action. This SOP produces a triage record and a recommendation, never an executed patch or an auto-closed ticket.
Inputs
cve_id (required): e.g. CVE-2021-44228 (Log4Shell).
scope (optional): an asset/subnet query to bound the correlation; default is the whole known inventory.
Step 1 — Establish the CVE's exploit signals
Pull the deterministic vulnerability facts (do not compute these yourself):
| Signal | Source tool | Field |
|---|
| Description + affected product/package | nvd_lookup | description, references |
| CVSS base score + vector | nvd_lookup | cvss_v3.baseScore, cvss_v3.vectorString |
| EPSS exploit probability | epss_kev | epss.score |
| CISA KEV known-exploited | epss_kev | kev.listed, kev.dateAdded |
Record UNKNOWN for any signal a tool cannot return. Derive the severity tier with the same fixed rules as the CVE triage rubric (KEV-listed or high-CVSS+high-EPSS → CRITICAL; degrade from there).
Step 2 — Identify the affected component signature
From the nvd_lookup description/references, extract the vulnerable component and, where stated, the affected version range. This is the matching key for the inventory correlation — e.g. Log4Shell → the log4j-core package below the patched version. If the component cannot be identified from the CVE data, flag it and fall back to matching on the CVE id itself against inventory cve_id / known_vuln markers.
Step 3 — Correlate to assets (the core step)
Query asset_lookup across the scope. For each host, classify it into exactly one bucket:
- AFFECTED: the vulnerable component/version is present, OR the inventory carries the matching
known_vuln/cve_id marker for this CVE.
- NOT-AFFECTED: the component is absent, or present at a patched version.
- INDETERMINATE: inventory lacks component/version data for this host → treat as AFFECTED for prioritization and record the gap in
unknowns.
For each AFFECTED asset, capture from asset_lookup:
- Exposure:
internet_exposed → internet-facing; else internal; else isolated.
- Criticality: crown-jewel / production / non-production (unknown → production).
- Exposed services/ports that surface the vulnerable component.
Step 4 — Compute the blast radius (deterministic)
Blast radius = the affected assets plus what an attacker reaches from them via asset_lookup trust_edges (SSH key reuse, shared admin creds, network reachability).
- Seed the set with every AFFECTED asset.
- Follow directed
trust_edges outward one hop at a time; add each reachable host as a downstream node (tag with the hop distance).
- Stop when no new nodes are added or a crown-jewel is reached (record the crown-jewel path explicitly — that is the headline risk).
Blast-radius size = count of affected + downstream nodes. Blast-radius severity = highest criticality reached (crown-jewel downstream of an internet-facing AFFECTED host is the worst case).
Step 5 — Urgency and action (deterministic mapping)
Combine severity tier (Step 1) with exposure + blast radius:
| Condition | Urgency | Default action |
|---|
| CRITICAL/HIGH tier + internet-facing AFFECTED + reaches crown-jewel | P1 | PATCH (emergency) + break the chokepoint |
| CRITICAL/HIGH tier + internet-facing or production AFFECTED | P1/P2 | PATCH (expedited) or MITIGATE if no patch |
| MEDIUM tier + AFFECTED | P3 | MITIGATE or schedule patch |
| Component absent everywhere (no AFFECTED) | — | NO-ACTION (document; re-check on inventory change) |
| Severity INDETERMINATE | — | HUMAN-REVIEW (blocking) |
MITIGATE = compensating control (WAF rule, config change, segmentation on a trust edge) when a patch is unavailable. Prefer chokepoint controls on shared trust edges — one control that shrinks the blast radius for several affected hosts.
Step 6 — Emit the CVETriage-shaped output
{
"cve_id": "CVE-XXXX-XXXXX",
"cvss_base_score": 0.0,
"cvss_vector": "string|UNKNOWN",
"epss_score": 0.0,
"kev_listed": false,
"severity_tier": "CRITICAL|HIGH|MEDIUM|LOW|INDETERMINATE",
"affected_component": "string|UNKNOWN",
"affected_assets": [
{"id": "web-01", "status": "AFFECTED", "exposure": "internet-facing",
"criticality": "production", "component_present": true}
],
"blast_radius"
requires_human_approval is always true.
Guardrails
- Never fabricate an affected-asset list — an asset is AFFECTED only on an
asset_lookup signal, INDETERMINATE otherwise. An empty affected list is a valid, honest result.
- Never fetch or execute proof-of-concept code; reference exploit status (KEV/EPSS) via
epss_kev only.
- If
epss_kev or nvd_lookup degrade, tier on whatever signal remains and flag the rest in unknowns — do not stall the asset correlation.
- Never patch, mitigate, or close a ticket autonomously; those are human actions downstream of this record.
- Feed
chokepoints into the attack-path-reasoning and detection-writing loops so the blast radius shrinks over time.