ASPM · Jul 23, 2026 · 7 min read

KEV and EPSS: the exploit funnel that turns 500 CVEs into 6 actions

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.

01What KEV actually certifies

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.

All CVEs in your build ~500 | v filter: component is imported and reachable via call graph Reachable CVEs ~60 | v filter: CVE appears in CISA KEV catalog Reachable + KEV ~4 | v filter: EPSS >= 1% (top 2% of all CVEs) Reachable + KEV or high-EPSS ~8 fix this week
Three sequential filters, not a sort. The funnel is applied left to right; EPSS adds the KEV-absent CVEs that threat intelligence predicts will be weaponized soon.

02EPSS: probability before the exploit ships

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.

epss-query.pypython
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"]
    ))

03Reachability is the prerequisite

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.

Proven - Criticallog4j-core 2.14.1 - CVE-2021-44228 (Log4Shell) - reachable via LogManager.getLogger
Call path: OrderController.createOrder ──► AuditService.logEvent ──► log4j-core:LogManager.getLogger (CVE-2021-44228) KEV: yes, added 2021-12-10 EPSS: 0.975 (97.5% 30-day exploitation probability)
CVSS 10.0 + KEV + EPSS 0.975 + reachable via two call hops from an HTTP entry point. Critical with a same-day SLA. Evidence: static call graph + SBOM component match.

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.

04Building the SLA from the combined signal

CVSS alone cannot anchor an SLA because it ignores exploitation reality. The combined signal - reachability, KEV, EPSS and CVSS as a tie-breaker - does:

ReachableOn KEVEPSSCVSSBandSLA
Yes - hot pathYesanyanyCriticalSame day / 24 h
YesNo>= 10%>= 7Critical7 days
YesNo1-9%>= 7High30 days
Dependency onlyYesanyanyHigh30 days
YesNo< 1%>= 9Medium90 days
NoYes or NoanyanyInformationalBacklog

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.

97%
CVEs with EPSS below 0.1%
7-day
SLA for reachable KEV findings
~8
fix-this-week items from 500 CVEs

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.

Evidence over heuristicsReachable beats foundMonitor → Block: rolling out a gate without slowing teamsIaC-grounded threat modelingAir-gapped AppSec with no phone-homeOne platform vs three tools
See it on your own app →