What VulnerabilityOps is
Link to section “What VulnerabilityOps is”In 30 seconds
Link to section “In 30 seconds”VulnerabilityOps is vulnerability management run as a continuous operation. Findings from scanners and software inventories arrive daily. A written policy sets each one’s urgency from exploitation evidence, the asset’s exposure and the possible damage; the finding goes to the asset’s owner with a due date, closes only when a rescan or test proves the fix, and the team watches whether fixes keep up with arrivals. The Cloud Security Alliance (CSA): “VulnOps reframes vulnerability management as a continuous flow problem rather than a queue problem.”[1] For analysts, security engineers, the owners who fix, and leaders.
At a glance
Link to section “At a glance”| Question | Answer |
|---|---|
| Goal | Exploitable exposure falls faster than new exposure arrives |
| Users | Vulnerability analysts, security engineers, asset and platform owners, CISOs |
| In scope | Coverage, CVE/CWE, CVSS limits, EPSS, KEV, SSVC (glossary), clocks, routing, verification, exception intake, disputes, flow metrics, feed quality |
| Out of scope | Patch rollout mechanics (PatchOps), pipeline gates (DevSecOps), risk-acceptance policy (RiskOps), incident response (ResponseOps) |
| Prerequisites | Inventory with owners and exposure tags; a scanner; ticketing |
| Key outputs | Remediation policy, dated owner queues, verification evidence, monthly flow report |
The problem it solves
Link to section “The problem it solves”The inflow grows: the CVE (Common Vulnerabilities and Exposures) Program published 18,375 records in 2020, 48,244 in 2025 and 35,872 in the first half of 2026 alone[5]. Capacity is limited: a 2019 study of about 300 organisations, by the Cyentia Institute for the vendor Kenna Security, found that “A typical organization, regardless of asset complexity, will have the capacity to remediate about one out of every 10 vulnerabilities in their environment within a given month”[6]. And exploited flaws go fast: for the 138 vulnerabilities disclosed in 2023 that Mandiant tracked as exploited in the wild (70% as zero-days), average time-to-exploit was five days after removing outliers (47 with them), down from 63 days in 2018–2019[7]. Few flaws are exploited, but those that are go fast, so exploitation evidence and exposure should set the clock. In Verizon’s 2026 report, exploitation was the top initial access vector in its breach dataset (31%), and organisations fully remediated only 26% of vulnerabilities in the Known Exploited Vulnerabilities (KEV) catalogue of CISA, the US Cybersecurity and Infrastructure Security Agency, in 2025 (38% in 2024), with a median of 43 days[8].
Without an operating model, programmes rank by Common Vulnerability Scoring System (CVSS) severity, ticket everything above a threshold and miss most dates. NIST, the US standards body, warns that “Overly simplistic metrics, such as counting the number of vulnerabilities … are not actionable”[9]. VulnerabilityOps replaces the ranked list with a policy that rations capacity on purpose, a pipeline that proves fixes, and metrics that show whether the system keeps up.
What it is not
Link to section “What it is not”- Not patch rollout. VulnerabilityOps decides what must be fixed and by when; PatchOps decides how to roll it out safely.
- Not pipeline security. Blocking vulnerable code before release is DevSecOps; this page handles what is deployed.
- Not risk-acceptance policy (RiskOps) or the owners-and-windows contract (SecOps).
- Not the supplier side. Flaws in products you sell are product security incident response (PSIRT) work, about vulnerabilities “within the products … which an organization produces and/or sells”[10].
- Not a tool. CSA says VulnOps does not replace scanners or patch platforms; it reorganises how their output is used[1].
Where it comes from and where it sits
Link to section “Where it comes from and where it sits”Origins and evolution of the label
Link to section “Origins and evolution of the label”Vulnerability management is decades old: NIST’s patch guidance reached its fourth revision in 2022[9]. CVSS has been the default severity signal since FIRST (the Forum of Incident Response and Security Teams) became its custodian in April 2005 (v4.0: November 2023)[11][12]; the newer signals are the Exploit Prediction Scoring System (EPSS; since April 2021; model v5 since 15 June 2026)[13], CISA’s KEV catalogue (2021)[14] and Stakeholder-Specific Vulnerability Categorization (SSVC) decision trees[15].
The label’s documented history is short: the earliest use found here is the October 2025 essay[2], though CSA says it “has been circulating in security operations communities for several years”. CSA’s May 2026 note names AI-driven discovery and NIST’s April 2026 triage of the National Vulnerability Database (NVD) among the forces behind it[1]. Independently, CISA’s Binding Operational Directive (BOD) 26-04 (June 2026) moved US federal policy the same way, replacing severity bands with exposure- and exploitation-based clocks[14].
Glossary and mental model
Link to section “Glossary and mental model”| Term | Meaning on this page |
|---|---|
| Vulnerability | “a weakness in an information system … that could be exploited or triggered by a threat source” (NIST)[16] |
| CVE record, CNA | Common Vulnerabilities and Exposures: one public ID per publicly disclosed vulnerability, published by CVE Numbering Authorities (CNAs)[17] |
| CWE | Common Weakness Enumeration, “a community-developed list of common software and hardware weaknesses”: the type of flaw[18] |
| CPE | Common Platform Enumeration: product identifiers that NVD enrichment adds and scanners depend on to match CVEs to software[19][1] |
| Finding (instance) | One vulnerability on one asset or image: the unit counted, routed and closed |
| CVSS; -B, -BT, -BE, -BTE | Common Vulnerability Scoring System; v4.0 scores are labelled by metric groups used: Base, Threat, Environmental[20] |
| EPSS | Exploit Prediction Scoring System: probability of exploitation activity in the next 30 days, with a percentile rank[21] |
| KEV | Known Exploited Vulnerabilities: CISA’s catalogue of vulnerabilities with reliable evidence of exploitation[22] |
| SSVC, ADP, Vulnrichment | Stakeholder-Specific Vulnerability Categorization: decision trees that output an action. CISA adds SSVC points to CVE records in its Authorized Data Publisher (ADP) container: “Vulnrichment”[23] |
| VEX | Vulnerability Exploitability eXchange: a supplier’s machine-readable statement that a product is or is not affected[24] |
| SBOM, SCA | Software bill of materials (component list); software composition analysis (matching dependencies to advisories) |
| FCEB | Federal Civilian Executive Branch: the US agencies BOD 26-04 binds[14] |
| Clock, saturation | Clock: maximum time to remediate from a trigger such as KEV listing or detection[14]. Saturation: arrivals ÷ remediation throughput (CSA)[1]; here, verified closures only |
| Verified fix | Closure backed by a rescan, configuration test, new image digest or upheld dispute, not by ticket status |
Mental model: a sink you will never empty. The tap is the arrival rate of findings, the drain the rate of verified fixes. Little’s Law: average items in a queuing system equal their arrival rate times the average time each spends in it[25]. Four levers: turn down the tap (fewer vulnerable components), widen the drain (automation, change windows), choose which water matters (prioritisation), check the drain (verification).
How VulnerabilityOps relates to other Ops
Link to section “How VulnerabilityOps relates to other Ops”| Neighbour | What the neighbour owns | Handoff with VulnerabilityOps |
|---|---|---|
| SecOps | Security–IT contract: owners, maintenance groups, exception register | Owners and windows in; joint scorecard out |
| PatchOps | Rings, windows, rollback, reboots | Dated, prioritised item out |
| DevSecOps | Build-time gates, SBOM production | SBOMs in; fixes for shipped code out |
| ContainerOps | Image builds, base-image updates | Rebuild request out |
| ThreatOps | Intelligence and hunts | Exploitation evidence in |
| SOC Ops, ResponseOps | Triage, forensics, incidents | Forensic-triage request out |
| RiskOps | Who may accept which risk | Exception request out |
| CloudSecOps, KubernetesSecOps, EndpointOps | Posture, admission, device agents | Exposure and coverage facts in |
Diagram source (Mermaid)
flowchart LR TI["ThreatOps: intelligence"] DS["DevSecOps: SBOMs"] INV["Inventory: owners, exposure"] SEC["SecOps: owners, windows"] V["VulnerabilityOps: what, by when, verified"] PO["PatchOps: rollout"] CO["ContainerOps: rebuilds"] RO["RiskOps: acceptance"] SO["SOC and ResponseOps: triage"] TI --> V DS --> V INV --> V SEC -.-> V V --> PO V --> CO V --> RO V --> SO classDef core fill:#e8f3f2,stroke:#16756f,color:#122029 classDef neutral fill:#f1f4f6,stroke:#5b7a87,color:#122029 class V core class TI,DS,INV,SEC,PO,CO,RO,SO neutral
Text equivalent: ThreatOps intelligence, DevSecOps SBOMs, the owner-and-exposure inventory and SecOps windows (dotted) feed VulnerabilityOps, which hands work to PatchOps (rollout), ContainerOps (rebuilds), RiskOps (acceptance) and SOC Ops and ResponseOps (triage).
Principles and operating model
Link to section “Principles and operating model”Charter and principles
Link to section “Charter and principles”- Ration on purpose. Focus on the highest risk, not all vulnerabilities equally, as BOD 26-04 does[14].
- Severity is an input, not a decision (see CVSS and its limits).
- Evidence beats prediction; prediction beats guessing. FIRST’s own advice: when a vulnerability appears on KEV, “treat it as actively exploited and prioritize accordingly, regardless of EPSS score”[26].
- The clock follows the facts. “The timelines for each asset are dynamic as facts change”[14].
- No owner, no fix. Every finding goes to the team that can change the asset, via the queue agreed in SecOps.
- Closed means verified (see Lifecycle).
- Measure the flow, not the pile.
- Remediation is engineering: “treating remediation as code”[1].
Non-goals: zero findings, scanner-score league tables, replacing the change process.
Roles, ownership and RACI
Link to section “Roles, ownership and RACI”R = responsible, A = accountable, C = consulted, I = informed. Atlas proposal; adapt the names.
| Activity | VulnerabilityOps | Asset owner | PatchOps / platform | Risk owner | SOC |
|---|---|---|---|---|---|
| Discovery and coverage | A/R | C | C | I | I |
| Remediation policy (versioned) | A/R | C | C | C | C |
| Tier and due date per finding | A/R | I | I | I | I |
| Fix or mitigate | C | A | R | I | I |
| Verify and close | A/R | I | C | I | I |
| Exception | R (intake) | R (request) | C | A | I |
| Forensic triage of exploited assets | C | C | C | I | A/R |
Analysts need systems basics, signal literacy, SQL or Python and diplomacy; small firms make it a part-time SecOps role.
Lifecycle
Link to section “Lifecycle”- Day 0, build: inventory and exposure tags, discovery sources, policy v1 with defaults, routing agreed with SecOps and PatchOps, verification rules.
- Day 1, start the clocks: tier the backlog (KEV first; do not try to clear it), open owner queues, publish a flow baseline.
- Day 2, run: the cadences below; automate the high-volume tail.
NIST’s life cycle is the same: know, plan, then prepare, implement, verify and continuously monitor[9].
Operating model and governance
Link to section “Operating model and governance”| Cadence | Forum | Output |
|---|---|---|
| Continuous | Pipeline (automated) | Tiered, dated findings in owner queues |
| Daily | KEV and emergency-advisory check | Tier-1 items, forensic-triage requests |
| Weekly | Owner sync per large owner group | Commitments and escalations |
| Monthly | Flow review with security leadership | Capacity or policy decision |
| Quarterly | Policy review | Policy version N+1 |
Escalation (Atlas proposal): a tier-1 item at half its clock without a plan goes to the owner’s manager; at the clock, to the CISO and the risk owner. CIS Safeguard 7.2 asks for a risk-based remediation process “with monthly, or more frequent, reviews”[27].
Architecture and workflow
Link to section “Architecture and workflow”Reference architecture and components
Link to section “Reference architecture and components”A pipeline with a policy engine in the middle; a commercial platform, open-source tools plus a database, or a spreadsheet can play these roles.
Diagram source (Mermaid)
flowchart TB S1["Host scans"] S2["SCA and image scans"] S3["Attack surface"] N["Normalise: asset x CVE"] P["Signals: KEV, EPSS, SSVC, VEX"] A["Context: owner, exposure"] E["Policy engine: tier, due date"] Q["Owner queues"] AU["Automated fix job"] X["Exception to RiskOps"] VF["Verify: rescan or test"] M["Flow metrics"] S1 --> N S2 --> N S3 --> N N --> E P --> E A --> E E --> Q E --> AU E --> X Q --> VF AU --> VF VF -- "still present" --> Q VF -- "fixed" --> M X --> M X -- "expired" --> Q classDef core fill:#e8f3f2,stroke:#16756f,color:#122029 classDef decision fill:#fdf3e3,stroke:#c9800f,color:#122029 classDef neutral fill:#f1f4f6,stroke:#5b7a87,color:#122029 class N,E,VF core class P,A decision class S1,S2,S3,Q,AU,X,M neutral
Text equivalent: Host, composition, image and attack-surface scans feed a normalisation step (one record per asset and CVE). A policy engine adds public signals and asset context and assigns a tier and due date. Work goes to owner queues, an automated fix job or an exception to RiskOps. Failed verifications and expired exceptions return to the owner queue; verified fixes and exceptions feed the flow metrics.
| Component | Design note |
|---|---|
| Normalisation | One record per asset × vulnerability × component; keep the first-seen date or every rescan resets the clock |
| Asset context | The weakest link; BOD 26-04 asks agencies to tag organisation, environment, exposure and asset type[14] |
| Signal store | Keep the daily snapshot used for each decision so an audit sees what you knew then |
| Policy engine | Deterministic, versioned like code, with explicit defaults for missing data |
| Verification | Authenticated scans or tests; ticket status alone never closes |
End-to-end workflow
Link to section “End-to-end workflow”Happy path. A new KEV entry matches two internet-facing gateways:
Diagram source (Mermaid)
sequenceDiagram participant SOC as SOC and ResponseOps participant V as VulnerabilityOps participant O as Asset owner participant P as PatchOps rollout Note over V: KEV lists a CVE: exposed, total control Note over V: Clock: 3 days plus forensic triage V->>V: Match inventory: 2 exposed gateways V->>SOC: Forensic triage request SOC-->>V: Volatile evidence collected V->>O: Tier 1 ticket, due in 3 days O->>P: Emergency change P-->>O: Fixed build, sessions reset O-->>V: Marked fixed V->>V: Authenticated rescan: not detected SOC-->>V: No evidence of compromise V->>V: Close with evidence
Text equivalent: A KEV entry giving total control of exposed assets sets a three-day clock with triage. VulnerabilityOps matches two gateways, asks the SOC to triage, then raises a tier-1 ticket. The owner’s emergency change applies the fix and resets sessions. A rescan no longer detects the flaw, the SOC finds no compromise, and the finding closes with its evidence.
Evidence comes first because CISA’s forensic triage steps say not to “alter or remediate systems prior to evidence/artifact collection when possible”[28].
Exception paths.
- No fix exists: mitigate and re-tier. SSVC notes that “Applying mitigations may change the value of decision points”, but a firewall rule is “only valid until someone changes the rule”[29].
- Owner cannot meet the clock: exception request with compensating control and expiry (RiskOps). BOD 26-04 allows none (“CISA does not issue waivers or exceptions”), and for KEV entries, apart from removing the asset, CISA calls the catalogue’s required actions “the only known technical mitigation”[28]; a control that leaves the service reachable does not count. The Atlas policy allows exceptions as a declared local change, but for KEV and T1 items only removal or isolation, for at most 7 days.
- Owner disputes the finding: see the false-positive practice in Core practices.
- Verification fails: back to the owner with evidence; the clock keeps running.
- Not affected: a justified supplier VEX
not_affectedstatement, or proof the component is absent, closes it[24]. - Compromise found: the incident moves to ResponseOps.
Prioritisation methods, standards and frameworks
Link to section “Prioritisation methods, standards and frameworks”Rule of thumb: use each signal for the question it answers.
| Signal | Question it answers | Publisher | Main limit |
|---|---|---|---|
| CVSS-B | How bad is the flaw in the worst reasonable case? | CNA or NVD (FIRST standard) | Not risk; often missing now |
| EPSS | How likely is exploitation activity in 30 days, across everyone? | FIRST EPSS SIG | Population-level |
| KEV | Has it been exploited, with a clear fix? | CISA | Lags; US-government lens |
| SSVC points | Automatable? How much control? | CISA Vulnrichment, your analysts | Gaps; snapshots |
| Exposure, criticality | Can an attacker reach it here, and does it matter? | You | Only as good as your inventory |
CVSS and its limits
Link to section “CVSS and its limits”CVSS v4.0 scores a flaw’s intrinsic characteristics, labelled CVSS-B, -BT, -BE or -BTE by the metric groups used[20]. The User Guide says the Threat group adjusts the “reasonable worst case” Base score using threat intelligence. Base scores “should not be used alone to assess risk”[30].
Use CVSS as a coarse severity filter and a source of technical impact. Expect gaps: since April 2026 NIST no longer routinely scores where the CNA has[31]. Many records carry only v3.1 scores (every scored record in Lab 1 does); use their Base score the same way, but the -B/-BT labels apply to v4.0 only.
EPSS: exploitation probability
Link to section “EPSS: exploitation probability”EPSS “estimates the probability that a published CVE will be exploited in the wild in the next 30 days”, daily, with a percentile rank[21]. Three facts from FIRST matter:
- The base rate is low. In any 30-day period FIRST sees exploitation activity for “anywhere from 1.5% to 3% of published vulnerabilities”, so a 50% threshold selects almost nothing. FIRST equates the effort of a “CVSS Critical” policy with roughly the 90th percentile (a score near 0.04), and “CVSS High and above (approximately the top 48% of published vulnerabilities)” with about 0.008[26].
- It is not about your environment. Localise it by presence, reachability and consequence[26]; FIRST lists “Observation bias in training labels” among its limits[32].
- Never combine it arithmetically with CVSS. “Do not multiply an EPSS score by an ordinal (such as CVSS)”[26].
FIRST frames the trade-off as coverage (share of exploited vulnerabilities you acted on) versus efficiency (share of your actions that hit exploited ones): recall and precision[32]. Model v5 replaced v4 on 15 June 2026, so time series shift there[13].
KEV: evidence of exploitation
Link to section “KEV: evidence of exploitation”CISA calls KEV “the authoritative source of vulnerabilities that have been exploited in the wild”[22].
- Entry rules. A CVE ID, reliable evidence of active exploitation, clear remediation and significant risk to US government systems; CISA “aims to update the KEV within 24 hours”[28]. Exploitability alone does not qualify; attempted exploitation, even against a honeypot, does[33].
- The feed. On 16 October 2026 the catalogue held 1,748 entries. Each JSON entry carries a
forensicTriageflag besidedueDate. Of the 131 entries added since BOD 26-04, 108 were due three days after listing (87 with triage) and 23 after fourteen days[34]. Counts change with each catalogue update; recount from the feed. dueDateis CISA’s default, computed from Table 1 with the evidence of public exposure it has; “the final determination for asset exposure is determined by the agency”[28]. Compute your own clock per asset (Lab 2).- Cautions. An old CVE newly added “does not necessarily indicate that CISA is currently observing active exploitation”, and KEV is not the only list to track[28].
SSVC and BOD 26-04: decisions instead of scores
Link to section “SSVC and BOD 26-04: decisions instead of scores”SSVC walks a decision tree to an action: CISA’s version ends in Track, Track*, Attend or Act[35]; the CERT/CC deployer model ends in Defer, Scheduled, Out-of-Cycle or Immediate from Exploitation, System Exposure, Automatable and Human Impact[29]. CISA publishes three decision points per CVE (Exploitation, Automatable, Technical Impact) as Vulnrichment data in the CVE record[23].
BOD 26-04 turns those points plus public exposure into clocks. It revoked BOD 19-02 and BOD 22-01, and its guidance notes that “the FCEB no longer requires CVSS use for vulnerability prioritization”[14][28]. Table 1, transcribed from the directive’s image (calendar days; “triage” = forensic triage within the same three days)[14]:
| Publicly exposed | In KEV | Automatable | Total control | Partial control |
|---|---|---|---|---|
| Yes | Yes | Yes | 3 days + triage | 3 days |
| Yes | Yes | No | 3 days + triage | 14 days |
| Yes | No | Yes | 3 days | 14 days |
| Yes | No | No | 14 days | 60 days |
| No | Yes | Yes | 3 days + triage | 14 days |
| No | Yes | No | 14 days | 14 days |
| No | No | Yes | 60 days | 60 days |
| No | No | No | Fix on system upgrade | Fix on system upgrade |
The clock starts at KEV listing or detection on an asset, “whichever event occurs first”; unknown Automatable and Technical Impact for a non-KEV CVE count as “no” and “total”; credential leaks count as total control; timelines are maximums[14][28].
Combining the signals
Link to section “Combining the signals”Diagram source (Mermaid)
flowchart TB
F["Finding: asset x CVE"] --> P{"Present and affected?"}
P -- "No: absent or VEX not_affected" --> C1["Close with evidence"]
P -- "Yes" --> K{"In KEV?"}
K -- "Yes" --> T1["Table 1 clock: 3 or 14 days"]
K -- "No" --> X{"Publicly exposed?"}
X -- "Yes" --> T2["Table 1 clock: 3, 14 or 60 days"]
X -- "No" --> A{"Automatable?"}
A -- "Yes" --> T3["60 days"]
A -- "No" --> E{"EPSS in the top 10%?"}
E -- "Yes" --> R["Weekly review: may move to 60 days"]
E -- "No" --> U["Fix on next upgrade"]
classDef decision fill:#fdf3e3,stroke:#c9800f,color:#122029
classDef risk fill:#fbeceb,stroke:#b4332b,color:#122029
classDef neutral fill:#f1f4f6,stroke:#5b7a87,color:#122029
class P,K,X,A,E decision
class T1,T2 risk
class C1,T3,R,U neutral
Text equivalent: Absent or “not affected” findings close with evidence. KEV findings get a Table 1 clock of 3 or 14 days; non-KEV findings on exposed assets 3, 14 or 60 days; internal automatable ones 60 days. Internal non-automatable findings wait for the next upgrade unless EPSS is in the top 10% (90th percentile or above), which triggers a weekly review (the Atlas’s addition).
Table 1 has no asset-criticality input. Its four facts say nothing about what the asset does, so an internal, non-automatable
flaw on a crown-jewel database waits for an upgrade, as on a test box. CISA handles this outside the table: agencies must document “explicit, transparent risk decisions that weigh mission needs—especially availability and
integrity of high value assets (HVAs) and mission critical functions—against potential impacts of exploitation”[28]; SSVC
carries it as Human Impact or Mission Prevalence[29][35]. The Atlas policy adds a criticality tag as a local
change: T4 findings on critical assets become T3, and promotions on them need no review.
Exploitation confirmed by a vendor or national CERT before CISA lists it can count as KEV by local policy; record the source. Flaws without CVE IDs (misconfigurations, custom web-application flaws) still need tiers: “The CVE program is not a comprehensive list of cybersecurity vulnerabilities”[28].
The data-quality limits of public feeds
Link to section “The data-quality limits of public feeds”- NVD enrichment is partial by policy. Since 15 April 2026 NIST prioritises CVEs in KEV, software used by the federal government and EO 14028 critical software; the rest are “lowest priority”, and backlogged CVEs published before 1 March 2026 moved to “Not Scheduled”[31][19]. On 17 October 2026 the NVD dashboard listed 57,074 CVEs as Deferred (the API name for Not Scheduled) and 7,563 awaiting enrichment (live counts; re-read the dashboard)[36]. Tools that rely on NVD CPE data will miss matches.
- Use more than one source. CSA notes that the CVE Program “has weathered repeated funding scares” and recommends pairing NVD with a source for the unenriched tail[1]; ENISA’s European Vulnerability Database is operational under NIS2[37].
Practices, playbooks and tools
Link to section “Practices, playbooks and tools”Core practices and playbooks
Link to section “Core practices and playbooks”| Practice | Trigger | Output | Verification |
|---|---|---|---|
| Coverage reconciliation | Weekly | Inventory assets without a recent authenticated scan | Coverage metric |
| KEV-day playbook | New KEV entry | Matched assets, clocks, triage requests | Rescan within the clock |
| Advisory triage | Vendor emergency advisory | Provisional tier before KEV or EPSS move | Re-tier when data arrives |
| Owner routing | New tiered finding | Ticket in the owner’s queue | Acknowledgement |
| Rebuild instead of patch | Image or immutable-host finding | New image digest deployed | Old digest gone from runtime |
| Exception intake | Clock cannot be met | Time-limited exception | Expiry check and rescan |
| False-positive dispute | Owner rejects a finding with evidence | Package-aware or authenticated recheck; internal “not affected” with reason and expiry | Rescan at expiry; dispute rate per plugin |
| Verification sweep | Ticket marked fixed | Closed with evidence, or reopened | Authenticated rescan or test (configuration review on OT) |
Two variants matter. Disputes: Red Hat warns that “just looking at the version number of a package will not tell them if they are vulnerable or not”; scanners that decide by version alone report backported fixes as false positives[38]. Without a dispute path such findings never close under a rescan rule, and owners stop trusting the queue. Rebuild-as-remediation: CSA argues that a vulnerable container should default to “an automated rebuild against a patched base image, with a tested rollback path, rather than a human ticket”[1]. For real flaws, NIST’s point stands: patching or upgrading are “the only forms of risk response that can completely eliminate the vulnerabilities without removing functionality”[9]. CIS Control 7 cadences: internal scans at least quarterly (authenticated and unauthenticated), external scans and remediation at least monthly[27].
Tooling landscape
Link to section “Tooling landscape”Examples verified 2026-10-16.
| Tool category | Job | Examples | Watch-outs |
|---|---|---|---|
| Network and host scanners | Missing patches, weak configurations | Tenable Nessus; Qualys[39]; Greenbone OpenVAS (open source)[40] | Unauthenticated scans miss local flaws[41]; credential failures look like progress |
| Image and SCA scanners | Match components to advisories | Trivy[42]; Grype (EPSS, KEV and OpenVEX support)[43] | Third-party code with access to your source[44] |
| Prioritisation platforms | Deduplicate, enrich, score, route | Commercial risk-based platforms; scanner vendors’ scores | Partly opaque: Qualys lists a CVE score’s inputs, not their weights[39] |
| Ticketing and IT service management (ITSM) | Owner queues, change records | Your existing ITSM | “Resolved” is not “verified” |
Choose on authenticated and appliance coverage, package-aware detection, VEX input, export APIs and transparent rules.
Automation and AI
Link to section “Automation and AI”Automate the deterministic steps first: ingestion, deduplication, enrichment, tiering, routing, rebuilds, rescans and reports. Keep people on policy changes, exceptions, disputes and risky changes to critical systems; SANS describes routing each system to “automatic patching, test before deployment, or manual review, with a human owning the final call”[3]. Language models can summarise advisories and draft tickets, but a wrong product or version match silently opens or closes findings: require a source link for every model claim and keep the policy engine deterministic.
VulnerabilityOps in practice
Link to section “VulnerabilityOps in practice”Reference implementation
Link to section “Reference implementation”The core artefact is a remediation policy written as data, versioned and read by the policy engine. This Atlas proposal adapts Table 1 for a non-federal organisation and lists every local change; the risk owner signs it.
Example status: syntax-checked (YAML), not deployed; values are illustrative.
policy: remediation-clocks
version: 3
effective: 2026-10-01
owner: vuln-mgmt@example.com
approved_by: risk-committee # RiskOps owns acceptance authority
basis: "CISA BOD 26-04 Table 1, adapted; local changes listed below"
inputs:
exposure: inventory.tags.exposure # public | internal
kev: cisa_kev_feed # daily snapshot
automatable: vulnrichment.ssvc.automatable # missing, non-KEV -> "no"
technical_impact: vulnrichment.ssvc.technical_impact # missing -> "total"
epss_percentile: first_epss_daily_file # daily snapshot
criticality: inventory.tags.criticality # critical | standard; not in Table 1
clock:
starts: earliest_of_kev_added_or_first_detected
unit: calendar_days
recompute_when: [exposure_changes, kev_added, mitigation_verified]
tiers: # "+t" in the decision table adds forensic triage
T1: {days: 3}
T2: {days: 14}
T3: {days: 60}
T4: {days: null, rule: next_scheduled_upgrade}
decision_table: # Table 1 as tiers: [total control, partial control]
public:
kev_automatable: [T1+t, T1]
kev_not_automatable: [T1+t, T2]
automatable: [T1, T2]
not_automatable: [T2, T3]
internal:
kev_automatable: [T1+t, T2]
kev_not_automatable: [T2, T2]
automatable: [T3, T3]
not_automatable: [T4, T4]
local_changes:
- "Exceptions are allowed (BOD 26-04 allows none); risk acceptance follows RiskOps."
- "Assets without an exposure tag are treated as public until tagged."
- "Past its clock at first detection: T1, due 3 days after detection, reported overdue."
- "Internal, non-automatable findings at EPSS percentile >= 0.90 get a weekly review."
- "Analysts may promote, never demote, a tier; the reason is recorded."
- "Criticality (absent from Table 1): T4 on a critical asset is T3; promotions there need no review."
verification:
closes_only_with: [authenticated_rescan, config_test, image_digest_change,
upheld_dispute]
ticket_status_alone: false
exceptions:
max_days: {T1: 7, T2: 30, T3: 90}
requires: [compensating_control, expiry_date, rescan_plan, risk_owner]
kev_or_T1: # CISA: required action or removal, nothing else
accepted_controls: [remove_from_network, isolate_vulnerable_service]
sign_off: [ciso, risk_owner]
reporting:
monthly: [saturation_by_tier, overdue_by_tier, coverage, dispute_rate,
exceptions_past_expiry]
The engine reads decision_table, which matches Table 1 and CERT/CC’s machine-readable BOD 26-04 table[45]. “Untagged =
public” is conservative and can flood the 3-day tier in a poorly tagged estate. CISA’s cheaper fallback: public if the asset has a
publicly routable IP address or the asset type “network”, or, for a KEV CVE, if the software is generally public-facing; otherwise
internal[28].
The second artefact, the hand-off record sent with each finding to the owner or PatchOps, freezes the signals behind the decision for the auditor.
Example status: syntax-checked (JSON); identifiers are fictitious.
{
"finding_id": "VF-2026-10-004211",
"asset": {"id": "vpn-gw-01", "owner_group": "network-ops",
"exposure": "public", "maintenance_group": "edge-appliances"},
"vulnerability": {"cve": "CVE-2023-4966", "advisory": "<vendor advisory URL>"},
"signals_snapshot": {"taken": "2026-10-16T09:00:00Z", "kev": true,
"kev_added": "2023-10-18", "epss": 0.99999,
"automatable": "yes", "technical_impact": "total"},
"decision": {"tier": "T1", "policy_version": 3, "clock_start": "2023-10-18",
"first_detected": "2026-10-14", "overdue_on_detection": true,
"due": "2026-10-17", "forensic_triage": true},
"required_actions": ["preserve volatile evidence", "install fixed build",
"kill active and persistent sessions"],
"verification": {"method": "authenticated_rescan",
"evidence": "credentialed checks: yes"},
"handoff": {"to": "patchops", "change_type": "emergency"}
}
Hands-on labs
Link to section “Hands-on labs”All three labs were executed on 2026-10-16 and re-run on 2026-10-17 (Ubuntu 20.04, Python 3.8.10); outputs are real. Lab 1’s live numbers change daily.
Lab 1 (beginner): enrich CVEs with KEV, EPSS and Vulnrichment
Link to section “Lab 1 (beginner): enrich CVEs with KEV, EPSS and Vulnrichment”Objective. See what each public signal says about the same CVEs, and where data is missing. Prerequisites. Python 3.8+, outbound HTTPS to cisa.gov, api.first.org and cveawg.mitre.org. Cost and risk. Free and read-only. In production, pull the daily EPSS file and the KEV JSON in bulk, which FIRST calls “more efficient than querying the API per CVE”[13]; per-CVE queries also reveal what you run.
- Create
cves.txtwith one ID per line:CVE-2023-4966,CVE-2021-44228,CVE-2024-3094,CVE-2020-14343,CVE-2023-38545,CVE-2024-6387. - Save the script as
enrich.py:
Example status: executed on 2026-10-17 against the live KEV feed (catalogVersion 2026.10.16), the EPSS API and the CVE Services API.
#!/usr/bin/env python3
"""Enrich CVE IDs with three free public signals: KEV, EPSS and Vulnrichment (SSVC)."""
import json
import sys
import urllib.request
KEV_URL = ("https://www.cisa.gov/sites/default/files/feeds/"
"known_exploited_vulnerabilities.json")
EPSS_URL = "https://api.first.org/data/v1/epss?cve="
CVE_URL = "https://cveawg.mitre.org/api/cve/"
HEADERS = {"User-Agent": "atlas-vulnops-lab/1.0 (contact: you@example.com)"}
def get_json(url):
req = urllib.request.Request(url, headers=HEADERS)
with urllib.request.urlopen(req, timeout=60) as resp:
return json.load(resp)
def ssvc_and_cvss(record):
"""Return CISA ADP SSVC options and the first CVSS base score (CNA, then ADP)."""
ssvc, cvss = {}, None
containers = [record["containers"]["cna"]] + record["containers"].get("adp", [])
for container in containers:
for metric in container.get("metrics", []):
other = metric.get("other", {})
if other.get("type") == "ssvc":
for option in other["content"]["options"]:
ssvc.update(option)
# The SSVC timestamp is not a reliable assessment date; the ADP
# container's dateUpdated says when CISA last changed its data.
ssvc["ts"] = other["content"].get("timestamp", "")[:10]
meta = container.get("providerMetadata", {})
ssvc["updated"] = meta.get("dateUpdated", "")[:10]
for key in ("cvssV4_0", "cvssV3_1", "cvssV3_0"):
if cvss is None and key in metric:
cvss = "%s %s" % (key[4:].replace("_", "."), metric[key]["baseScore"])
return ssvc, cvss
def main(path):
cves = [line.strip() for line in open(path) if line.strip()]
kev = {v["cveID"]: v for v in get_json(KEV_URL)["vulnerabilities"]}
epss = {row["cve"]: row for row in get_json(EPSS_URL + ",".join(cves))["data"]}
rows = []
for cve in cves:
ssvc, cvss = ssvc_and_cvss(get_json(CVE_URL + cve))
k = kev.get(cve, {})
e = epss.get(cve, {})
rows.append({
"cve": cve,
"kev": bool(k),
"kev_added": k.get("dateAdded", ""),
"ransomware": k.get("knownRansomwareCampaignUse", ""),
"epss": float(e.get("epss", 0.0)),
"percentile": float(e.get("percentile", 0.0)),
"exploitation": ssvc.get("Exploitation", "n/a"),
"automatable": ssvc.get("Automatable", "n/a"),
"technical_impact": ssvc.get("Technical Impact", "n/a"),
"ssvc_ts": ssvc.get("ts", ""),
"adp_updated": ssvc.get("updated", ""),
"cvss": cvss or "none",
})
print("%-14s %-3s %-10s %-7s %5s %5s %-6s %-4s %-7s %-10s %s" % (
"cve", "kev", "kev_added", "ransom", "epss", "pctl", "exploit", "auto", "impact",
"adp_upd", "cvss"))
for r in rows:
print("%-14s %-3s %-10s %-7s %5.3f %5.3f %-6s %-4s %-7s %-10s %s" % (
r["cve"], "yes" if r["kev"] else "no", r["kev_added"] or "-",
r["ransomware"] or "-",
r["epss"], r["percentile"], r["exploitation"], r["automatable"],
r["technical_impact"], r["adp_updated"] or "-", r["cvss"]))
with open("enriched.json", "w") as out:
json.dump(rows, out, indent=1)
if __name__ == "__main__":
main(sys.argv[1] if len(sys.argv) > 1 else "cves.txt")
- Run
python3 enrich.py cves.txt.
Expected output (17 October 2026; EPSS values rounded to three decimals):
cve kev kev_added ransom epss pctl exploit auto impact adp_upd cvss
CVE-2023-4966 yes 2023-10-18 Known 1.000 1.000 active yes total 2026-07-31 V3.1 9.4
CVE-2021-44228 yes 2021-12-10 Known 1.000 1.000 active yes total 2025-10-21 V3.1 10
CVE-2024-3094 no - - 0.909 0.998 none yes total 2024-07-30 V3.1 10
CVE-2020-14343 no - - 0.060 0.932 n/a n/a n/a - none
CVE-2023-38545 no - - 0.787 0.996 poc no total 2025-08-27 V3.1 8.8
CVE-2024-6387 no - - 0.995 0.999 poc no total 2024-07-02 V3.1 8.1
What to notice. CVE-2020-14343 has no CVSS score or SSVC data: the enrichment gap your policy must default.
exploit is CISA’s view as of its ADP container’s last update (adp_upd): CVE-2024-3094 reads none from July 2024 while its EPSS
is high today. The SSVC timestamp (ssvc_ts in the JSON) is no assessment date: CVE-2023-4966’s reads 2023-10-10, before the ADP programme began in 2024[23]. A score of 0.060 sits at the 93rd
percentile because the base rate is so low. Validation. Spot-check one row on
the KEV website and the EPSS API. Troubleshooting. HTTP 403 or 429: slow down, keep a descriptive User-Agent; HTTP Error 404: the ID is
reserved or unassigned (no record yet); a rejected record (cveMetadata.state REJECTED) prints a row of n/a and none. Teardown. Keep enriched.json for Lab 2, then delete it.
Lab 2 (intermediate): turn the policy into due dates
Link to section “Lab 2 (intermediate): turn the policy into due dates”Objective. Apply Table 1, CISA’s defaults and the Atlas’s EPSS review rule to findings. Prerequisites. Lab 1’s enriched.json; PyYAML for the table check. Cost and risk. None; synthetic assets.
- Save the findings as
findings.csv:
Example status: executed on 2026-10-17 as input to the script below.
asset,cve,exposed,detected
vpn-gw-01,CVE-2023-4966,yes,2026-10-14
app-17,CVE-2021-44228,no,2026-10-15
build-04,CVE-2024-3094,no,2026-10-12
etl-02,CVE-2020-14343,no,2026-09-20
web-09,CVE-2023-38545,yes,2026-10-01
bastion-1,CVE-2024-6387,yes,2026-10-10
db-03,CVE-2024-6387,no,2026-10-10
- Save the script as
policy.py:
Example status: executed on 2026-10-17 (Python 3.8.10); the built-in self-test passed.
#!/usr/bin/env python3
"""Compute remediation due dates from BOD 26-04 Table 1 plus local asset exposure.
Usage: python3 policy.py enriched.json findings.csv 2026-10-16
"""
import csv
import json
import sys
from datetime import date, timedelta
# BOD 26-04 Table 1 (CISA, 2026-06-10), keyed by
# (publicly exposed, in KEV, automatable, technical impact) -> calendar days.
# None means "fix on system upgrade"; "triage" adds a forensic triage.
TABLE_1 = {
(True, True, True, "total"): (3, "triage"),
(True, True, True, "partial"): (3, ""),
(True, True, False, "total"): (3, "triage"),
(True, True, False, "partial"): (14, ""),
(True, False, True, "total"): (3, ""),
(True, False, True, "partial"): (14, ""),
(True, False, False, "total"): (14, ""),
(True, False, False, "partial"): (60, ""),
(False, True, True, "total"): (3, "triage"),
(False, True, True, "partial"): (14, ""),
(False, True, False, "total"): (14, ""),
(False, True, False, "partial"): (14, ""),
(False, False, True, "total"): (60, ""),
(False, False, True, "partial"): (60, ""),
(False, False, False, "total"): (None, ""),
(False, False, False, "partial"): (None, ""),
}
def decision_inputs(signal):
"""Defaults for missing Vulnrichment data: CISA's for non-KEV CVEs ("no", "total");
for a KEV CVE (CISA says it always enriches these) assume "yes", "total"."""
auto = signal["automatable"]
impact = signal["technical_impact"]
if auto not in ("yes", "no"):
auto = "yes" if signal["kev"] else "no"
if impact not in ("total", "partial"):
impact = "total"
return auto == "yes", impact
def main(signals_path, findings_path, today):
signals = {s["cve"]: s for s in json.load(open(signals_path))}
print("%-9s %-14s %-3s %-3s %-4s %-7s %-10s %-10s %s" % (
"asset", "cve", "exp", "kev", "auto", "impact", "start", "due", "status"))
for f in csv.DictReader(open(findings_path)):
s = signals[f["cve"]]
exposed = f["exposed"] == "yes"
auto, impact = decision_inputs(s)
days, extra = TABLE_1[(exposed, s["kev"], auto, impact)]
detected = date.fromisoformat(f["detected"])
start = detected
if s["kev"]:
start = min(detected, date.fromisoformat(s["kev_added"]))
if days is None:
due, status = "upgrade", "-"
if s["percentile"] >= 0.90:
status = "pctl %.3f: review" % s["percentile"]
else:
due_day = start + timedelta(days=days)
due = due_day.isoformat()
late = (today - due_day).days
status = "LATE %dd" % late if late > 0 else "due in %dd" % -late
if extra:
status += " +triage"
print("%-9s %-14s %-3s %-3s %-4s %-7s %-10s %-10s %s" % (
f["asset"], f["cve"], "Y" if exposed else "N", "Y" if s["kev"] else "N",
"Y" if auto else "N", impact, start.isoformat(), due, status))
def self_test():
assert len(TABLE_1) == 16
assert TABLE_1[(True, True, True, "total")] == (3, "triage")
assert TABLE_1[(True, False, False, "partial")] == (60, "")
assert TABLE_1[(False, False, False, "total")] == (None, "")
unknown = {"kev": False, "automatable": "n/a", "technical_impact": "n/a"}
assert decision_inputs(unknown) == (False, "total")
assert decision_inputs(dict(unknown, kev=True)) == (True, "total")
if __name__ == "__main__":
self_test()
main(sys.argv[1], sys.argv[2], date.fromisoformat(sys.argv[3]))
- Run
python3 policy.py enriched.json findings.csv 2026-10-16.
Expected output:
asset cve exp kev auto impact start due status
vpn-gw-01 CVE-2023-4966 Y Y Y total 2023-10-18 2023-10-21 LATE 1091d +triage
app-17 CVE-2021-44228 N Y Y total 2021-12-10 2021-12-13 LATE 1768d +triage
build-04 CVE-2024-3094 N N Y total 2026-10-12 2026-12-11 due in 56d
etl-02 CVE-2020-14343 N N N total 2026-09-20 upgrade pctl 0.932: review
web-09 CVE-2023-38545 Y N N total 2026-10-01 2026-10-15 LATE 1d
bastion-1 CVE-2024-6387 Y N N total 2026-10-10 2026-10-24 due in 8d
db-03 CVE-2024-6387 N N N total 2026-10-10 upgrade pctl 0.999: review
What to notice. Under the “whichever event occurs first” rule, an old KEV vulnerability found today is overdue on discovery; your
policy must say what that means (the Atlas policy: tier 1, due three days after detection, reported overdue). pctl is the EPSS
percentile, not a probability. The xz backdoor (CVE-2024-3094) gets 60 days on an internal build host, neither in KEV nor exposed:
where criticality tags and promotion earn their place. The same CVE gets different clocks on bastion-1 and db-03: exposure sets
the clock. Validation. Set db-03 to exposed=yes
and rerun; the row should read due in 8d. Then save the reference policy as policy.yaml and check with check_table.py (needs
PyYAML) that the hand-written TABLE_1 matches its decision_table:
Example status: executed on 2026-10-17 (PyYAML 5.3.1): rows: 16, mismatches: 0; a copy with one changed cell exits 1.
#!/usr/bin/env python3
"""Rebuild TABLE_1 from the policy file's decision_table and compare (needs PyYAML).
Usage: python3 check_table.py policy.yaml (in the folder that holds policy.py)
"""
import sys
import yaml
from policy import TABLE_1
ROWS = {"kev_automatable": (True, True), "kev_not_automatable": (True, False),
"automatable": (False, True), "not_automatable": (False, False)}
def table_from_policy(path):
policy = yaml.safe_load(open(path))
days = {tier: spec["days"] for tier, spec in policy["tiers"].items()}
table = {}
for exposure, rows in policy["decision_table"].items():
for row, cells in rows.items():
kev, auto = ROWS[row]
for impact, cell in zip(("total", "partial"), cells):
tier, _, triage = cell.partition("+")
table[(exposure == "public", kev, auto, impact)] = (
days[tier], "triage" if triage else "")
return table
if __name__ == "__main__":
table = table_from_policy(sys.argv[1])
wrong = [key for key in TABLE_1 if table.get(key) != TABLE_1[key]]
print("rows: %d, mismatches: %d" % (len(table), len(wrong)))
for key in wrong:
print("mismatch", key, "policy:", table.get(key), "TABLE_1:", TABLE_1[key])
sys.exit(1 if wrong else 0)
Troubleshooting. KeyError on a CVE: rerun Lab 1 with it. Teardown. None.
Lab 3 (advanced): measure the flow
Link to section “Lab 3 (advanced): measure the flow”Objective. Compute saturation, backlog, age and a Little’s Law estimate, and see how each misleads when read alone.
Prerequisites. Python 3.8+. Cost and risk. None; the default run generates a synthetic 24-week log (seed 7). Your own export
needs id,opened_week,closed_week (blank = open), with closed_week the week the fix was verified.
- Save the script as
flow.py:
Example status: executed on 2026-10-17 (Python 3.8.10), synthetic mode and a four-row CSV.
#!/usr/bin/env python3
"""Flow metrics for a remediation pipeline: arrivals, verified closures, saturation, age.
Usage: python3 flow.py (synthetic 24-week log, seed 7)
python3 flow.py export.csv (columns: id,opened_week,closed_week; blank = open)
"""
import csv
import math
import random
import sys
WEEKS = 24
def poisson(rng, mean):
limit, k, p = math.exp(-mean), 0, 1.0
while True:
p *= rng.random()
if p <= limit:
return k
k += 1
def synthetic(seed=7):
"""500 items already open; arrivals step from 100 to 150 a week at week 13;
capacity near 120 verified closures a week; nothing is fixed in its first week;
70% of capacity goes to the newest items (urgent work jumps the queue)."""
rng = random.Random(seed)
items = [{"id": n, "opened": -rng.randrange(26), "closed": None} for n in range(500)]
for week in range(1, WEEKS + 1):
for _ in range(poisson(rng, 100 if week <= 12 else 150)):
items.append({"id": len(items), "opened": week, "closed": None})
ready = [i for i in items if i["closed"] is None and i["opened"] < week]
ready.sort(key=lambda i: -i["opened"])
capacity = poisson(rng, 120)
newest, rest = ready[:int(0.7 * capacity)], ready[int(0.7 * capacity):]
rng.shuffle(rest)
for item in newest + rest[:capacity - len(newest)]:
item["closed"] = week
return items
def load(path):
rows = csv.DictReader(open(path))
return [{"id": r["id"], "opened": int(r["opened_week"]),
"closed": int(r["closed_week"]) if r["closed_week"] else None} for r in rows]
def pct(values, q):
values = sorted(values)
return values[min(len(values) - 1, int(q * len(values)))] if values else 0
def report(items, window=4):
weeks = max(max(i["opened"], i["closed"] or 0) for i in items)
print("weeks arrive close rho backlog age_p50 age_p90 L/lambda")
for end in range(window, weeks + 1, window):
start = end - window + 1
arrive = sum(1 for i in items if start <= i["opened"] <= end)
close = sum(1 for i in items if i["closed"] and start <= i["closed"] <= end)
still_open = [i for i in items if i["opened"] <= end
and (i["closed"] is None or i["closed"] > end)]
ages = [end - i["opened"] for i in still_open]
lam = arrive / window
print("%2d-%-2d %6d %5d %5.2f %7d %7d %7d %8.1f" % (
start, end, arrive, close, arrive / max(close, 1), len(still_open),
pct(ages, 0.5), pct(ages, 0.9), len(still_open) / lam if lam else 0))
still_open = [i for i in items if i["closed"] is None]
stale = [i for i in still_open if weeks - i["opened"] > 8]
print("open longer than 8 weeks: %d of %d" % (len(stale), len(still_open)))
last = [i for i in items if weeks - window < i["opened"] <= weeks]
shut = [i for i in items if i["closed"] and weeks - window < i["closed"] <= weeks]
print("backlog growth now: %+.1f items/week" % ((len(last) - len(shut)) / window))
if __name__ == "__main__":
report(load(sys.argv[1]) if len(sys.argv) > 1 else synthetic())
- Run
python3 flow.py(orpython3 flow.py export.csvfor your own data).
Expected output:
weeks arrive close rho backlog age_p50 age_p90 L/lambda
1-4 366 484 0.76 382 15 26 4.2
5-8 420 453 0.93 349 4 29 3.3
9-12 353 485 0.73 217 4 30 2.5
13-16 619 466 1.33 370 1 12 2.4
17-20 613 505 1.21 478 2 7 3.1
21-24 607 507 1.20 578 3 10 3.8
open longer than 8 weeks: 88 of 578
backlog growth now: +25.0 items/week
What to notice. While saturation (rho) stays below 1 the backlog shrinks; after arrivals step up in week 13 it stays above 1
and the backlog grows by about 25 items a week. The trap: the 90th-percentile age falls from 30 to 12 weeks just as things get
worse, because new items dilute the old ones. Never report age without saturation and backlog. L/lambda
(average weeks in the system) is reliable only when arrivals and closures roughly balance. Validation. Recompute one window by hand.
Troubleshooting. Zero arrivals print L/lambda as 0; partial windows at the end are skipped. Teardown. None.
Use cases and scenarios
Link to section “Use cases and scenarios”| Scenario | Source | Core problem | VulnerabilityOps move | Success measure |
|---|---|---|---|---|
| Log4Shell, December 2021 | Sourced | Where is the component? | Estate-wide dependency queries | Time to locate every instance |
| Citrix Bleed, October 2023 | Sourced | “Patched” was not “safe” | Fix definition includes session kill and hunt | No sessions predating the fix |
| Plant with unpatchable controllers | Illustrative | No fix for years | Mitigate, re-tier, expiring exception | Mitigation re-verified quarterly |
Log4Shell (sourced). The Cyber Safety Review Board (2022) reported that one federal cabinet department spent 33,000 hours on Log4j response, called Log4j an “endemic vulnerability”, and found that organisations using SBOMs reported none had used them to find vulnerable Log4j deployments[46]. CSA’s bar: a programme “should be able to answer “where is this dependency in our stack?” within minutes”[1]. Build that query (SBOM index, agent package inventory, image metadata) before the next crisis; an index of your own builds misses appliances and commercial software.
Citrix Bleed (sourced). CVE-2023-4966 let attackers “bypass password requirements and multifactor authentication (MFA), leading to successful session hijacking”, and LockBit 3.0 affiliates exploited it[47]. After installing fixed builds, NetScaler advised: “we also recommend killing all active and persistent sessions”[48]. Closing on a version change left stolen sessions alive: the fix definition must list every action (patch, kill sessions, rotate credentials, hunt), and verification must check each.
Plant with unpatchable controllers (illustrative scenario). Firmware is certified once a year. Isolate the controllers behind a monitored gateway, re-tier, issue an expiring exception owned by the plant manager, and re-verify the isolation quarterly by configuration review. Do not point active scanners at the controllers: NIST warns that active scans “may cause device instability” in operational technology (OT); use passive discovery and test scanners offline first[49].
Measure, govern, pay
Link to section “Measure, govern, pay”Metrics and KPIs
Link to section “Metrics and KPIs”CSA recommends to “instrument the inbound rate of vulnerability findings against the throughput of remediation, treat the ratio as a saturation metric, and report it to security leadership monthly”[1]. Formulas are the Atlas’s unless marked.
| Metric | Formula | Pitfall | Cadence |
|---|---|---|---|
| Saturation ρ (CSA; “verified” is the Atlas’s) | Arrivals ÷ verified closures, same window, per tier | Read with backlog age | Monthly |
| Backlog growth | Arrivals − verified closures per week | Deduplication changes counts | Weekly |
| Time in system | Little’s Law: W ≈ backlog L ÷ arrival rate λ[25] | Valid only near balance | Monthly |
| On-time verified fixes | Verified by due date ÷ due in window; open overdue count as misses | Measure at verification | Monthly per tier |
| Credentialed coverage | In-scope inventory assets with a good authenticated or agent scan in 7 days ÷ in-scope assets | Denominator from the scanner | Weekly |
| Enrichment gap | Open findings missing exposure, SSVC or CVSS inputs ÷ open findings | Defaults hide it | Weekly |
| Recurrence | Reopened within 90 days of a verified fix ÷ verified fixes | Stale golden images | Monthly |
| Remediation change failure | Remediation changes causing incident or rollback ÷ remediation changes | Shared with PatchOps | Monthly |
| Dispute rate | Upheld disputes ÷ findings, per scanner and plugin | Owners who stop disputing and ignore | Monthly |
| Exception debt | Exceptions past expiry; open exception-days | Exceptions outside the register | Monthly |
Diagram source (Mermaid)
flowchart TB M["Monthly review: saturation, age, coverage"] L1["Reduce inflow"] L3["Ration with tiers"] L2["Raise capacity"] L4["Verify every fix"] IN["Arrivals: CVEs, assets, regressions"] B["Open backlog"] OUT["Verified closures"] M --> L1 M --> L3 M --> L2 M --> L4 L1 -.-> IN L3 -.-> B L2 -.-> OUT L4 -.-> OUT IN --> B B --> OUT OUT -- "flow counts" --> M classDef core fill:#e8f3f2,stroke:#16756f,color:#122029 classDef decision fill:#fdf3e3,stroke:#c9800f,color:#122029 classDef neutral fill:#f1f4f6,stroke:#5b7a87,color:#122029 class IN,B,OUT core class M decision class L1,L2,L3,L4 neutral
Text equivalent: A monthly review of saturation, age and coverage pulls four levers: reduce inflow (acts on arrivals of CVEs, assets and regressions), ration with tiers (the open backlog), raise capacity and verify every fix (verified closures). Arrivals pass through the backlog to closures; a “flow counts” edge from closures back to the review stands for all three counts.
Worked calculation (illustrative numbers). September arrivals and verified closures by tier: T1 12 and 11, T2 180 and 190, T3 600 and 520 (T4 is tracked separately). Saturation: T1 1.09 (one item: review the item, not the ratio), T2 0.95, T3 1.15. The T3 backlog is 1,300 with 600 arrivals a month, so Little’s Law gives 1,300 ÷ 600 ≈ 2.2 months, about 65 days: the average T3 item takes longer than its 60-day clock, and with saturation above 1 the real figure is rising. Coverage is 1,820 of 1,900 in-scope assets, 95.8%; the 80 missing hosts lost scan credentials in a key rotation (see the runbook). Decision: automate the two largest T3 sources, restore coverage, and report T3 saturation until it stays below 1.
Targets. Baseline for two or three months, then set per tier: saturation below 1 over a quarter, zero overdue T1, coverage above a floor. Never reward closed-ticket counts.
Quality, security and compliance
Link to section “Quality, security and compliance”- Scanner credentials are privileged. Reliable Windows patch checks need local administrator rights[41]. Vault the credentials, scope them per segment, rotate them through change control and alert on use outside scan windows.
- Scanners are third-party code. In March 2026 attackers redirected Trivy’s GitHub Action tags and published a malicious Trivy v0.69.4[44]. Pin scanners by digest, verify checksums and keep them away from unrelated secrets (DevSecOps).
- Findings are an attack map. Restrict and log access to the findings database and exports.
- Rules. FedRAMP will require its new Vulnerability Detection and Response and Vulnerability Evaluation and Reporting rules by 7 December 2026 to align with BOD 26-04[28]. Since 11 September 2026 the EU Cyber Resilience Act requires manufacturers to report actively exploited vulnerabilities (early warning within 24 hours)[50]. It is supplier-side but adds advisories to ingest.
- Evidence. Keep policy version, signal snapshot, decision and verification per finding (ComplianceOps).
Cost, capacity and sustainability
Link to section “Cost, capacity and sustainability”The main cost is fixing time, not licences; the 2019 Kenna/Cyentia study found top performers closed roughly 2.5 times more than others[6]. Plan with verified fixes per engineer-week by tier and the share automated. Cheapest first: reduce inflow (unused packages, standard base images, decommissioning), then automate high-volume fixes, then buy tools.
Assurance of the operating model
Link to section “Assurance of the operating model”- Each month, rescan a random sample of closed findings with a different method or account.
- Reconcile inventory and scan results weekly, and your exposed-asset list with an external attack-surface scan.
- Alert when the KEV snapshot, EPSS date or advisory feed is more than a day old.
When things go wrong
Link to section “When things go wrong”Common failures and troubleshooting
Link to section “Common failures and troubleshooting”| Symptom | Likely cause | Diagnosis | Fix and prevention |
|---|---|---|---|
| Findings drop sharply; dashboard turns green | Scan credential or path failure | Authenticated coverage by segment; Nessus plugin 21745[41] | Restore access, rescan; coverage alert |
| Same finding reopens monthly | Golden image or baseline reintroduces it | Recurrence by image or template | Fix the source; rebuild pipeline |
| Clocks reset on every rescan | Deduplication keyed on scan ID | Compare first-seen dates | Key on asset × CVE × component; fixtures |
| Serious CVE gets a low tier | Missing CVSS/CPE data, weak defaults | Enrichment-gap report | Explicit defaults; analyst promotion |
| Owners ignore or reject tickets | No agreed routing or capacity; version-only false positives (backports) | Overdue by owner group; dispute rate by plugin; package changelog or vendor advisory | Escalate; agree windows in SecOps; package-aware checks and a dispute path |
| Exploited although “fixed” | Incomplete fix definition | Post-incident review | List every action in the hand-off record |
| Scan missed a vulnerable system | Scope or method gap | Inventory versus scan scope | Agent or authenticated scan; reconciliation |
A precedent for the last row: Equifax told GAO that a scan a week after the Apache Struts announcement “did not detect the vulnerability on the online dispute portal”, and the alert had gone to an out-of-date recipient list[51].
Worked runbook: the dashboard turned green overnight
Link to section “Worked runbook: the dashboard turned green overnight”Scenario (illustrative). Monday’s report shows open findings down 18% and tier-2 saturation at 0.4, with no patch campaign.
Diagram source (Mermaid)
flowchart TB
S["Findings drop sharply"] --> Q1{"Credentialed coverage dropped too?"}
Q1 -- "Yes" --> C1["Check failed-login results per segment"]
C1 --> C2{"Cause found?"}
C2 -- "Credential rotated or expired" --> F1["Restore vaulted credential, rescan"]
C2 -- "Firewall or agent change" --> F2["Restore scan path, rescan"]
F1 --> R["Mark the gap window low-confidence"]
F2 --> R
Q1 -- "No" --> Q2{"Matching fixes in change records?"}
Q2 -- "Yes" --> V["Re-verify a sample, accept the drop"]
Q2 -- "No" --> D["Check deduplication and scanner version"]
R --> P["Add coverage alert; rotate via change process"]
classDef risk fill:#fbeceb,stroke:#b4332b,color:#122029
classDef decision fill:#fdf3e3,stroke:#c9800f,color:#122029
classDef core fill:#e8f3f2,stroke:#16756f,color:#122029
class S risk
class Q1,C2,Q2 decision
class F1,F2,R,V,D,P core
Text equivalent: If findings drop sharply, check whether credentialed coverage dropped too. If so, read failed-login results per segment, restore the credential or scan path, rescan, mark the gap window low-confidence, then add a coverage alert and rotate credentials through change control. If coverage held and change records show matching fixes, re-verify a sample and accept the drop; otherwise check deduplication and scanner version.
- Triage (first hour). Compare credentialed coverage by segment with last week. Here plugin 19506, which reports per host
whether credentialed checks are possible[52], no longer shows
Credentialed checks : yesfor 80 Linux hosts in two segments, and plugin 21745 reports failed logins; on Linux that status also needs the package inventory[41]. - Diagnose. A job rotated the scan account’s SSH key on Friday without updating the scanner’s vault entry.
- Remediate. Update the vault entry, rescan both segments, confirm the open count returns to about last week’s level (undoing the 18% drop).
- Communicate. Annotate the report (lost coverage, not progress) and recompute saturation for those weeks.
- Prevent. Alert when a segment’s authenticated coverage falls 5 points in a week; add the scan account to the rotation process.
Exit criteria: coverage restored, report corrected, alert live, rotation process updated.
Anti-patterns and myths
Link to section “Anti-patterns and myths”- “CVSS 9+ first” as the whole policy. Severity is not risk (EPSS). Instead: exposure and exploitation clocks with CVSS as one input.
- What FIRST calls “Score Laundering”. Multiplying EPSS by CVSS, or a weighted “risk score” nobody can explain[26]. Instead: a traceable decision table.
- “Patch everything.” A wish, not a plan; it guarantees silent rationing by whoever shouts loudest.
- Myth: low EPSS means safe. A 5% score means about 1 in 20 such vulnerabilities see exploitation activity within 30 days[32].
Deciding and adopting
Link to section “Deciding and adopting”When to use and when not to use
Link to section “When to use and when not to use”Use the model when arrivals outrun verified fixes for months, tier-1 clocks are missed, scanners overlap, or nobody can answer “are we getting better?”
Do not start here when:
- There is no inventory with owners. Build it first with ITOps and SecOps.
- The estate is small and patches itself. Ten SaaS-heavy laptops need patch compliance (EndpointOps, PatchOps) and a KEV watch, not a pipeline.
- You run mainly SaaS. The provider fixes the service; you need contractual timelines and status reports (CISA’s advice to agencies) and a process for what stays yours: access, authentication, data and audit logging[28], plus endpoints.
- The bottleneck is change approval. Faster triage only lengthens the change-board queue; fix that first (ITOps).
Hidden costs: tagging, a policy owner, analyst time for promotions, exceptions and disputes, and the politics of publishing what you will not fix.
Decision framework
Link to section “Decision framework”Diagram source (Mermaid)
flowchart TB
A{"Inventory with owners and exposure tags?"}
A -- "No" --> B["Build inventory first"]
A -- "Yes" --> C{"Saturation above 1 or tier-1 clocks missed?"}
C -- "No" --> D["Keep the current process; add KEV and EPSS signals"]
C -- "Yes" --> E{"Main bottleneck?"}
E -- "Deciding" --> F["Risk-based policy and flow metrics"]
E -- "Fixing" --> G["Remediation as code with platform teams"]
E -- "Both" --> H["Full VulnerabilityOps: one pipeline, one policy"]
F --> I["Monthly saturation review"]
G --> I
H --> I
classDef decision fill:#fdf3e3,stroke:#c9800f,color:#122029
classDef core fill:#e8f3f2,stroke:#16756f,color:#122029
classDef neutral fill:#f1f4f6,stroke:#5b7a87,color:#122029
class A,C,E decision
class F,G,H,I core
class B,D neutral
Text equivalent: Without an inventory of owners and exposure tags, build it first. Then, if saturation is at or below 1 and tier-1 clocks are met, keep the current process and add KEV and EPSS signals. Otherwise find the bottleneck: deciding (risk-based policy and flow metrics), fixing (remediation as code with platform teams) or both (full VulnerabilityOps). Every path ends in a monthly saturation review.
The judgement call compares CVSS bands with Table 1-style clocks; a commercial platform is a third option (less engineering, proprietary scores).
Maturity model
Link to section “Maturity model”This is the Atlas’s model, not an industry standard; exit criteria are examples.
| Level | Observable behaviour | Exit criterion to next level |
|---|---|---|
| 1. Scan and react | Periodic scans, CVSS-ranked spreadsheets | Inventory with owners for in-scope assets |
| 2. Owned queues | Findings routed to owners with dates; KEV watched | Written tiered policy; authenticated coverage measured |
| 3. Risk-based policy | Exposure and exploitation clocks, explicit defaults, expiring exceptions | Closure requires verification; saturation reported monthly |
| 4. Flow-managed | Saturation, age, coverage and recurrence drive capacity decisions | High-volume classes automated; no overdue T1 for a quarter |
| 5. Adaptive | Remediation as code by default; VEX and reachability cut noise; policy reviewed against outcomes | Sustain; review yearly |
Adoption roadmap: 30/60/90 days
Link to section “Adoption roadmap: 30/60/90 days”| Days | Actions | Milestone |
|---|---|---|
| 1–30 | Inventory gap list; tag internet-facing assets; daily KEV match; baseline arrivals, closures, coverage | Baseline report; KEV matches routed within one business day |
| 31–60 | Policy v1 with defaults; hand-off record; rescan-based closure; exception intake with RiskOps | Policy signed; on-time T1 measured |
| 61–90 | Automate one high-volume class (e.g. base-image rebuilds); first monthly flow review; coverage alert | Saturation per tier reported; one class automated |
Toolkit
Link to section “Toolkit”Checklists
Link to section “Checklists”Quick start (first two weeks)
- In-scope assets exported with owner and exposure; a written default for untagged assets
- Daily KEV match against the inventory, routed within one business day
- EPSS and Vulnrichment pulled for open CVEs (Lab 1)
- Baseline: arrivals, verified closures, backlog, credentialed coverage
KEV-day readiness (preflight)
- On-call contact per owner group of internet-facing assets
- Emergency change route agreed with PatchOps and ITOps
- Forensic triage request agreed with the SOC; evidence before fix where possible[28]
- Out-of-band channel for suspected compromise; rescan job that targets named assets within hours
Monthly flow review
- Saturation per tier, with backlog and age beside it
- Overdue tier-1 and tier-2 items by owner, with reasons
- Coverage, enrichment-gap and dispute-rate trends; exceptions past expiry
- One decision recorded: capacity, policy or inflow
Governance audit (quarterly)
- Policy version, approver and changelog current
- 20 sampled closures each have verification evidence; sampled tiers match the policy in force
- Scanner credentials vaulted, scoped, rotated through change control; findings access reviewed
Continuous improvement
- Top three recurring findings fixed at their source; one manual remediation class automated per quarter
Templates
Link to section “Templates”One-page remediation policy summary:
REMEDIATION POLICY v3 - summary effective 2026-10-01
Owner: Vulnerability management Approved by: Risk committee
Purpose: reduce exploitable exposure faster than it arrives.
Clocks (calendar days, from KEV listing or first detection, whichever is first):
T1 3 days (+ forensic triage if in KEV and total control)
T2 14 days T3 60 days T4 next scheduled upgrade
Inputs: public exposure, KEV, automatable, technical impact, EPSS percentile,
asset criticality (local: T4 on a critical asset = T3).
Defaults: untagged asset = public; missing automatable = no; missing impact = total.
Found already past its clock: T1, due 3 days after detection, reported overdue.
Closure: authenticated rescan, configuration test, new image digest or upheld dispute.
Exceptions (a local change; BOD 26-04 allows none): max T1 7, T2 30, T3 90 days;
compensating control, expiry and risk owner signature. KEV and T1 items: removal
or isolation only, signed by the CISO and the risk owner.
Promotion: analysts may raise a tier with a recorded reason, never lower one.
Review: monthly flow review; policy review every quarter.
Exception request (VulnerabilityOps intake; the approval policy belongs to RiskOps):
EXCEPTION REQUEST ID: EXC-____ Policy: v__
Finding(s): ______________________ Assets: _______________ Tier/clock: ____
Why the clock cannot be met: ____________________________________________
Compensating control: __________________________________________________
(KEV or T1: removal or isolation only - segmentation, software-defined perimeter, proxy)
Which decision point it changes (exposure / automatable / impact): ________
How the control is verified, and how often: ______________________________
Requested expiry (max T1 7, T2 30, T3 90 days): ________ Rescan on: _______
Requested by (asset owner): ___________ Approved by (risk owner): ________
KEV or T1 only - CISO sign-off: ______________________
Monthly flow review record:
FLOW REVIEW Month: ________ Attendees: _____________________________
Tier | Arrivals | Verified closures | Saturation | Backlog | Oldest open
T1 | | | | |
T2 | | | | |
T3 | | | | |
Coverage (credentialed): ____% Enrichment gap: ____% Recurrence: ____%
Exceptions past expiry: ___ Remediation change failures: ___ Disputes upheld: ___
Decision taken (capacity / policy / inflow): ______________________________
Owner and date: _______________________________________________________
Deep comparisons
Link to section “Deep comparisons”| Approach | Unit of work | Primary question | Choose when |
|---|---|---|---|
| Traditional vulnerability management | Scan report | What did the scanner find? | Small estates that patch automatically |
| VulnerabilityOps (this page) | Finding instance in a flow | Are we fixing what matters faster than it arrives? | Saturated programmes, many sources |
| Exposure management (continuous threat exposure management, CTEM; “VOC” in vendor material[4]) | Attack path, incl. misconfigurations and identities | What can an attacker reach? | Mature programmes adding validation |
Is VulnerabilityOps just vulnerability management renamed? Partly: same tasks, plus explicit rationing, verified closure and flow metrics[1].
Should we stop using CVSS? No; use it for severity and technical impact, not as clocks. CISA notes that labels such as “critical” or “high” “do not prescribe specific actions”[28].
We are not a US federal agency. Does BOD 26-04 apply? Not legally, unless a contract says so. Adapt Table 1 and write down where you differ, as the reference policy does.
What if a CVE has no CVSS score or product data? Apply CISA’s explicit defaults (see Table 1), add a second source for the unenriched tail and track the gap. NIST will consider enrichment on request, as resources allow[31].
An owner says a finding is a false positive. Who decides? The analyst, with a package-aware or authenticated check. An upheld dispute closes the finding with its evidence and an expiry; nothing closes silently.
Going further
Link to section “Going further”Advanced topics and emerging direction
Link to section “Advanced topics and emerging direction”- More inflow from AI-assisted discovery (emerging). The 2025 essay expects AI vulnerability research to become available “at scale, repeatable, and integrated into enterprise operations”[2]; plan capacity for rising arrivals, not last year’s.
- Measuring what KEV misses (research). NIST CSWP 41 (May 2025) proposes a “Likely Exploited Vulnerabilities” metric that may augment EPSS and measure KEV’s comprehensiveness[53].
- VEX and reachability at scale (emerging; reachability: DevSecOps). Each VEX statement is its author’s assertion[24]; never let a team approve VEX relief for its own service.
- New defect classes. CSA notes that prompt-injection chains and tool-poisoning of agent integrations do not map cleanly onto CVE taxonomies but are still vulnerabilities in the operational sense[1].
Learning path
Link to section “Learning path”| Stage | What to do |
|---|---|
| Prerequisites | SecOps; basic Linux, Windows and networking; reading a vendor advisory |
| 1-day primer | The CVSS User Guide’s “CVSS Base Score (CVSS-B) Measures Severity, not Risk”[30], “Using EPSS”[26], KEV criteria[33], BOD 26-04 Table 1[14]; Lab 1 |
| 1-week practice | Labs 2 and 3 on your own scanner export; policy v1 in YAML |
| 30 days | One monthly flow review; coverage reconciliation; hand-off record agreed with PatchOps |
| Specialisation | CVSS-BTE enrichment[54]; custom SSVC trees[29]; VEX ingestion; LEV[53] |
| Portfolio | Open-source enrichment script; anonymised comparison of CVSS bands versus Table 1; coverage dashboard |
Beginners stop after the primer; leaders read the decision framework and the judgement call.
Wrap-up
Link to section “Wrap-up”Executive recap and cheat sheet
Link to section “Executive recap and cheat sheet”| Topic | Remember |
|---|---|
| Definition | Vulnerability management run as a flow: tier, route, verify, measure |
| Decide with | KEV (evidence) > EPSS (probability) > CVSS (severity), localised by exposure and impact |
| Model to adapt | BOD 26-04 Table 1: exposure, KEV, automatable, impact → 3, 14, 60 days or next upgrade |
| Never | Multiply EPSS by CVSS; close on ticket status; grant exceptions without expiry or for KEV items without isolation |
| Metrics | Saturation per tier, backlog and age, on-time verified fixes, coverage, enrichment gap |
| Watch-outs | Partial NVD enrichment; credential failures that look like progress; scanners as supply-chain risk |
| Next actions | Tag exposure; match KEV daily; write policy v1; require rescans; review saturation monthly |
A judgement call: replace CVSS bands with exposure-and-exploitation clocks?
Link to section “A judgement call: replace CVSS bands with exposure-and-exploitation clocks?”Situation (illustrative). A 4,000-asset company fixes CVSS 9.0+ in 15 days and 7.0–8.9 in 30. About 1,300 high or critical finding instances arrive a month against capacity for about 1,000 verified fixes: saturation 1.3, on-time near 60%.
Option A: adopt Table 1-style clocks. A shadow run on last quarter’s data puts about 150 instances a month in the 3- and 14-day tiers, 500 in the 60-day tier and the rest at “next upgrade”. Assumptions: exposure tags are accurate, CISA’s defaults are applied, capacity stays at 1,000.
Option B: keep the bands and add “any KEV item: 7 days”.
Why B is weaker than it looks. The bands still mark roughly half of published CVEs as urgent[26], so saturation stays above 1, due dates become fiction, and the KEV override competes with hundreds of “critical” internal items no attacker can reach.
Why A can fail. It moves risk into the inventory: an internet-facing host tagged internal waits 60 days or for an upgrade,
and without a criticality input crown-jewel internal systems wait too. Default “total” impact pushes items up, and auditors may ask
why “critical” items wait.
Decision process. Run both policies in shadow for 60 days; fix exposure tags first; let analysts promote any CVSS 9.0+ internal
item after review. Adopt A if tier-1 and tier-2 saturation stays below 1 in the shadow period and an external attack-surface scan
finds no public asset tagged internal. Uncertainty: one quarter of data; arrivals may rise. If tags cannot be trusted within
60 days, B with honest capacity reporting is the safer interim choice.
Learning objectives and mastery check
Link to section “Learning objectives and mastery check”After this page you can explain why CVSS alone is not a policy, apply Table 1 and EPSS, design a policy, hand-off record and verification rule, operate a flow review and evaluate whether you need the model.
1. Tier-2 arrivals were 240 in a month and verified closures 200. What is saturation, and what follows?
240 ÷ 200 = 1.2. The tier-2 backlog grows by about 40 a month, so time in system rises and on-time performance falls unless inflow drops or capacity rises.
2. A non-KEV, automatable CVE with partial control sits on an internet-facing web server. What does Table 1 say?
14 days. If the server is taken off the internet, the row becomes “not exposed, automatable”: 60 days. The clock follows the facts.
3. Why not multiply EPSS by CVSS to get a risk score?
EPSS is a calibrated probability; CVSS is an uncalibrated ordinal, so the product has no meaning (FIRST’s “Score Laundering”). Use a decision table in which each signal answers its own question.
4. The T3 backlog holds 900 items and about 450 arrive each month. What does Little’s Law suggest?
W ≈ 900 ÷ 450 = 2 months, about 60 days (valid only near balance; a growing backlog means longer). The mean equals the clock, so a large share of items will be late; aim for a mean well below the clock.
5. Open findings fell 18% in a week with no patch campaign. What do you check first?
Credentialed coverage by segment. Failed logins or broken scan paths make findings vanish; a credentialed result needs a login and a package inventory. If coverage fell, the improvement is not real.
6. A leader asks for “zero critical vulnerabilities by year end”. How do you respond?
The goal measures the pile and is unreachable while arrivals exceed capacity. Offer instead: no overdue tier-1 items, saturation below 1 for tiers 1–3, coverage above an agreed floor and every exception within expiry, in the monthly flow report.
Conclusion
Link to section “Conclusion”VulnerabilityOps is a new name for an old job done in a better order: start from capacity, decide openly from evidence rather than severity alone, give each item an owner and a date, prove every fix and watch the flow. The label may fade; CISA’s clocks, FIRST’s probabilities and Little’s arithmetic will still apply. If you remember one thing: you will never fix everything, so make sure what you leave unfixed is a decision, not an accident.
Sources and further reading
Link to section “Sources and further reading”Further reading: EPSS FAQ. Evidence ledger: research/vulnerabilityops-ledger.md.
Changelog: 2026-10-16 r0: first draft, labs executed. 2026-10-17 r1: review fixes (origins, study populations, exceptions, disputes, acronyms, decision table, OT scanning); labs re-run. 2026-10-17 r2: criticality input added to the policy; Lab 1 troubleshooting corrected and re-tested; Figure 3 labels shortened; related list, runbook units and User Guide reference fixed. 2026-10-17, post-score polish: NIST definition; Qualys precision; Figure 5 uncrossed; KEV and CVSS prose split; live-count notes; Lab 2 policy-table check; DevSecOps link; repetitions cut. Checker fixes: KEV note, Figure 5 text, Lab 2 prerequisites.
VulnOps: Vulnerability Management in the Age of AI — Cloud Security Alliance, 2026-05-04. Checked 2026-10-17. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
Autonomous AI Hacking and the Future of Cybersecurity — Adkins, Evron, Schneier (CSO essay, reposted), 2025-10-10. Checked 2026-10-16. ↩︎ ↩︎ ↩︎
VulnOps: A Guide to Starting Implementation From A CISO — SANS Institute blog (briefing announcement), 2026-08-05. Checked 2026-10-17. ↩︎ ↩︎
The Case for a Vulnerability Operations Center — Check Point (vendor blog), 2026-05-15. Checked 2026-10-17. ↩︎ ↩︎
Prioritization to Prediction, Volume 3 — Cyentia Institute for Kenna Security, 2019. Checked 2026-10-16. ↩︎ ↩︎
How Low Can You Go? An Analysis of 2023 Time-to-Exploit Trends — Mandiant, Google Cloud, 2024-10-16. Checked 2026-10-17. ↩︎
2026 Data Breach Investigations Report: Executive Summary — Verizon, 2026. Checked 2026-10-17. ↩︎
NIST SP 800-40 Rev. 4: Guide to Enterprise Patch Management Planning — NIST, April 2022. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎
PSIRT Services Framework v1.1 — FIRST. Checked 2026-10-16. ↩︎
CVSS v1 Archive — FIRST (“FIRST to host CVSS”, 14 April 2005). Checked 2026-10-17. ↩︎
Common Vulnerability Scoring System — FIRST. Checked 2026-10-16. ↩︎
EPSS: Get the Data — FIRST. Checked 2026-10-17. ↩︎ ↩︎ ↩︎
BOD 26-04: Prioritizing Security Updates Based on Risk — CISA, 2026-06-10. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
Stakeholder-Specific Vulnerability Categorization — CISA. Checked 2026-10-16. ↩︎
Vulnerability — NIST CSRC Glossary. Checked 2026-10-17. ↩︎
Vulnerability Status — NIST NVD, updated 2026-04-14. Checked 2026-10-17. ↩︎ ↩︎
CVSS v4.0 Specification Document — FIRST, document version 1.2. Checked 2026-10-16. ↩︎ ↩︎
Exploit Prediction Scoring System — FIRST. Checked 2026-10-16. ↩︎ ↩︎
Known Exploited Vulnerabilities Catalog — CISA. Checked 2026-10-17. ↩︎ ↩︎
CISA Vulnrichment — CISA. Checked 2026-10-17. ↩︎ ↩︎ ↩︎
Minimum Requirements for VEX — CISA, April 2023. Checked 2026-10-16. ↩︎ ↩︎ ↩︎
Little’s Law as Viewed on Its 50th Anniversary — John D. C. Little, Operations Research, 2011. Checked 2026-10-16. ↩︎ ↩︎
Using EPSS — FIRST. Checked 2026-10-17. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
Control 7: Continuous Vulnerability Management — CIS Controls Assessment Specification v8.1. Checked 2026-10-16. ↩︎ ↩︎
BOD 26-04: Implementation Guidance — CISA, 2026-06-10. Checked 2026-10-17. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
Prioritizing Patch Deployment — CERT/CC SSVC documentation. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎
CVSS v4.0 User Guide — FIRST. Checked 2026-10-17. ↩︎ ↩︎
NIST Updates NVD Operations to Address Record CVE Growth — NIST, 2026-04-15. Checked 2026-10-17. ↩︎ ↩︎ ↩︎
How EPSS Works — FIRST. Checked 2026-10-16. ↩︎ ↩︎ ↩︎
Reducing the Significant Risk of Known Exploited Vulnerabilities — CISA. Checked 2026-10-17. ↩︎ ↩︎
KEV JSON feed — CISA, version 2026.10.16; counts by the Atlas. Checked 2026-10-17. ↩︎
CISA SSVC Guide — CISA, November 2022. Checked 2026-10-16. ↩︎ ↩︎
NVD Dashboard — NIST NVD, live. Checked 2026-10-17. ↩︎
Consult the European Vulnerability Database — ENISA, 2025-05-13. Checked 2026-10-16. ↩︎
Security Backporting Practice — Red Hat. Checked 2026-10-17. ↩︎
Finding-Level Scores: QVS, QDS, and QVSS — Qualys, updated September 2026. Checked 2026-10-17. ↩︎ ↩︎
Greenbone Community Edition: Background — Greenbone. Checked 2026-10-16. ↩︎
Tenable Nessus Credentialed Checks — Tenable. Checked 2026-10-17. ↩︎ ↩︎ ↩︎ ↩︎
trivy image — Trivy documentation. Checked 2026-10-16. ↩︎
anchore/grype — Anchore. Checked 2026-10-16. ↩︎
Update: Ongoing Investigation and Continued Remediation — Aqua Security (Trivy incident post), 2026-03-22, updated 2026-04. Checked 2026-10-17. ↩︎ ↩︎
CISA BOD 26-04 decision table, v1.0.0 (JSON) — CERT/CC SSVC repository, pinned commit. Checked 2026-10-17. ↩︎
Review of the December 2021 Log4j Event — Cyber Safety Review Board, 2022-07-11. Checked 2026-10-16. ↩︎
AA23-325A: LockBit 3.0 Affiliates Exploit CVE 2023-4966 Citrix Bleed — CISA and partners, 2023-11-21. Checked 2026-10-16. ↩︎
CVE-2023-4966: Critical security update now available — NetScaler, October 2023. Checked 2026-10-16. ↩︎
NIST SP 800-82 Rev. 3: Guide to Operational Technology Security — NIST, September 2023 (§6.3.2.4; Appendix E.2.3). Checked 2026-10-17. ↩︎
Cyber Resilience Act: Reporting obligations — European Commission. Checked 2026-10-16. ↩︎
Data Protection: Actions Taken by Equifax and Federal Agencies (GAO-18-559) — US GAO, 2018-08-30. Checked 2026-10-17. ↩︎
Nessus Scan Information (plugin 19506) — Tenable. Checked 2026-10-17. ↩︎
NIST CSWP 41: Likely Exploited Vulnerabilities — NIST, 2025-05-19. Checked 2026-10-16. ↩︎ ↩︎
CVSS v4.0 Consumer Implementation Guide — FIRST, 2026-01-16. Checked 2026-10-16. ↩︎