CVSS measures theoretical severity. KEV certifies active exploitation. EPSS predicts it. Combined with reachability, they collapse a 500-CVE backlog to the handful that need action this week.
CVSS was designed to measure theoretical severity. It says nothing about whether a vulnerability is being exploited right now, whether any working code exists, or whether your specific stack is reachable. Sorting 500 CVEs by CVSS 9.8 and working down the list hands your team a queue that is mostly noise. The two signals that change this are KEV membership and EPSS probability - and together with reachability they collapse that list to the items that need action this week.
The CISA Known Exploited Vulnerabilities catalog is not a theoretical severity ranking. CISA adds a CVE only after documented evidence of active exploitation in the wild: a confirmed incident, threat intelligence from a reliable source, or a well-attributed campaign. A CVE on KEV is not a high score - it is a case file.
The practical implication for prioritization is asymmetric. A CVE absent from KEV might still matter (the catalog lags real-world activity by days to weeks, and it focuses on CVEs that affect government and critical infrastructure). But a CVE that is on KEV with a reachable, imported component in your application is not a backlog item - it is an incident waiting for a ticket number.
The Exploit Prediction Scoring System (EPSS), maintained by FIRST, models the 30-day probability that a CVE will be observed being exploited in the wild. The model is trained on roughly 100 features: PoC availability, vendor, product, CVSS vector components, social media activity, threat-intelligence feeds and historical exploitation patterns. It outputs a probability, not a severity band, which means it can be threshold-driven and combined with other signals.
The distribution is extreme. More than 90% of published CVEs sit below 0.1% EPSS. A CVE at 1% is already in the top 2-3% of all known vulnerabilities by exploitation probability. A CVE above 10% is approaching near-certainty of active exploitation within 30 days - the question is whether it is in your stack, not whether someone will try.
import requests def fetch_epss(cves): # FIRST EPSS v3 API - one call for a batch of CVEs url = "https://api.first.org/data/1.0/epss" params = {"cve": ",".join(cves)} resp = requests.get(url, params=params, timeout=10) resp.raise_for_status() return { row["cve"]: float(row["epss"]) for row in resp.json()["data"] } def prioritize(findings, kev_set, epss_scores): for f in findings: f["on_kev"] = f["cve"] in kev_set f["epss"] = epss_scores.get(f["cve"], 0.0) f["fix_now"] = f["reachable"] and (f["on_kev"] or f["epss"] >= 0.01) return sorted(findings, key=lambda x: ( -x["on_kev"], -x["epss"], -x["cvss"] ))
Neither KEV nor EPSS knows whether the vulnerable component is actually in your dependency graph and reached by a live code path. A CVE with EPSS 40% in a library your application imports but never calls is a different risk than the same CVE in a library that sits on the hot path of every authenticated request. The funnel only works if reachability filters first.
Call-graph reachability means tracing from an entry point - an HTTP handler, a background job, a CLI invocation - through the import chain to the vulnerable function in the flagged package version. A dependency that is in package.json but not imported, or imported but the vulnerable function is never called, is not reachable. Marking it as such, rather than surfacing it as a full finding, is what keeps the actionable list short enough to trust.
When the component is imported but the vulnerable function is never called, the finding is surfaced as reachable-dependency (present in the graph) rather than reachable (reached from an entry point). The distinction is what drives the SLA: reachable is a 7-day critical; reachable-dependency with KEV membership is a 30-day high. Both appear in the posture; only one is treated as an incident.
CVSS alone cannot anchor an SLA because it ignores exploitation reality. The combined signal - reachability, KEV, EPSS and CVSS as a tie-breaker - does:
| Reachable | On KEV | EPSS | CVSS | Band | SLA |
|---|---|---|---|---|---|
| Yes - hot path | Yes | any | any | Critical | Same day / 24 h |
| Yes | No | >= 10% | >= 7 | Critical | 7 days |
| Yes | No | 1-9% | >= 7 | High | 30 days |
| Dependency only | Yes | any | any | High | 30 days |
| Yes | No | < 1% | >= 9 | Medium | 90 days |
| No | Yes or No | any | any | Informational | Backlog |
The not-reachable row is the one that surprises teams the most. A CVSS 10 that is not reachable in your application is an informational finding, not an incident. That is the correct rating. Treating it as critical burns team capacity on risk you do not carry. EPSS and KEV do not change that - they escalate reachable findings, they do not override the reachability gate.
The funnel is not a shortcut around rigor - it is rigor applied in the right order. Reachability confirms your surface. KEV confirms active exploitation. EPSS surfaces the near-term threats KEV has not yet cataloged. CVSS breaks ties within a band. The queue at the bottom is short because each filter is precise; the items in it are actionable because every one has a fact, not a score, driving its position.