Risk-based prioritization with CVSS v4, EPSS and KEV
How to combine CVSS v4 severity, EPSS exploitation probability, the CISA KEV catalog and asset context into priority tiers teams can meet.
Every vulnerability program eventually hits the same wall: scanners report far more findings than the organization can fix, and the default sort order — CVSS base score, highest first — produces a backlog in which a large share of items are labelled "High" or "Critical". When everything is urgent, nothing is. Remediation teams stop trusting the queue, SLAs are missed across the board, and the vulnerabilities attackers actually use get the same treatment as those nobody will ever exploit.
Risk-based prioritization fixes this by asking three separate questions instead of one: how bad is it if exploited (severity), how likely is it to be exploited (threat), and how much does this asset matter to us (context). This guide shows how the main public data sources — CVSS v4, EPSS and the CISA Known Exploited Vulnerabilities (KEV) catalog — answer the first two questions, and how to combine them with your own asset data into a small number of tiers that map cleanly to remediation SLAs.
Why the CVSS base score alone fails
The Common Vulnerability Scoring System is maintained by FIRST, and its own specification is explicit that a Base score describes the intrinsic characteristics of a vulnerability, not the risk it poses to a particular organization. The base score assumes the worst reasonable case: the vulnerable component is reachable, configured in the vulnerable way, and exposed to an attacker. It says nothing about whether anyone is exploiting it, and nothing about whether the affected host runs your payment platform or a forgotten test VM.
In practice, three problems follow from using base scores as the priority:
- Compression at the top. A large share of published CVEs score 7.0 or above, so a sort by base score does not separate the truly urgent from the merely serious.
- Blindness to exploitation. Many vulnerabilities that attackers use in real intrusions score in the Medium or High band, not Critical. A pure score cut-off pushes them down the queue.
- No asset context. A Critical finding on an isolated, non-production host can matter less than a High on an internet-facing identity provider.
CVSS v4 in one page
CVSS v4.0 was published by FIRST on 1 November 2023. It keeps the 0–10 scale and the qualitative bands (None 0.0, Low 0.1–3.9, Medium 4.0–6.9, High 7.0–8.9, Critical 9.0–10.0), but reorganizes the metrics into four groups:
| Metric group | What it captures | Changes the score? |
|---|---|---|
| Base | Intrinsic exploitability and impact: Attack Vector, Attack Complexity, Attack Requirements (new), Privileges Required, User Interaction (None / Passive / Active), impact on the Vulnerable System (VC/VI/VA) and on Subsequent Systems (SC/SI/SA) | Yes |
| Threat | Exploit Maturity: Attacked, POC, Unreported, or Not Defined | Yes |
| Environmental | Your security requirements and any modified base metrics reflecting your deployment | Yes |
| Supplemental | Safety, Automatable, Recovery, Value Density, Vulnerability Response Effort, Provider Urgency | No — context only |
Two changes matter most for a VOC. First, v4 removed the old Scope metric and instead scores impact on the vulnerable system and on subsequent systems separately, which makes "this bug lets you pivot" explicit. Second, the specification introduced nomenclature that tells you which groups were used: CVSS-B (base only), CVSS-BT (base + threat), CVSS-BE (base + environmental) and CVSS-BTE (all three). A score published by a vendor or by NVD is almost always CVSS-B. The number you actually want for prioritization is closer to CVSS-BTE — and the Threat and Environmental inputs are exactly what EPSS, KEV and your inventory provide.
The Supplemental metrics are worth reading even though they do not move the score. Automatable: Yes (an attacker can script exploitation across many targets) and Safety impacts are useful tie-breakers, and the Automatable concept appears again in SSVC decision trees.
EPSS: probability of exploitation activity
The Exploit Prediction Scoring System is produced by a FIRST special interest group. For each published CVE it gives a probability between 0 and 1 that exploitation activity will be observed in the next 30 days, plus a percentile that ranks the CVE against all others. Scores are recalculated daily from a model trained on real-world exploitation signals, and the fourth major model version was released in March 2025.
Keep three properties in mind:
- EPSS is about likelihood, not impact. A high EPSS score on a low-impact information disclosure is still low impact. Use EPSS alongside severity, never instead of it.
- Scores move. A CVE can jump when a proof of concept is published or exploitation is observed. Pull scores daily or at least weekly rather than once at detection time.
- Percentile and probability answer different questions. The percentile is useful for ranking; the raw probability is what you should threshold on, because percentiles shift as the population of CVEs grows.
The API is free and requires no key. A request such as https://api.first.org/data/v1/epss?cve=CVE-2021-44228 returns JSON with an epss probability, a percentile and the date of the model run.
KEV: evidence, not prediction
The CISA Known Exploited Vulnerabilities catalog was created under Binding Operational Directive 22-01 in November 2021. A CVE is added only when there is reliable evidence of active exploitation and a clear remediation action exists. Each entry includes the vendor and product, a short description, the required action, a dueDate that is binding for US federal civilian agencies, and a knownRansomwareCampaignUse field set to Known or Unknown.
For everyone else, KEV is the single most valuable prioritization signal available for free: it converts "might be exploited" into "is being exploited". It is deliberately conservative — a CVE missing from KEV is not proof of safety — so treat it as a floor, not a ceiling. Vendor advisories, threat-intelligence feeds and your own SOC's observations can and should add to it.
Adding asset context
Threat data tells you which vulnerabilities attackers like; only your asset inventory tells you where they would hurt. At minimum, you need two attributes per asset:
- Business criticality — typically three or four tiers, agreed with the business, based on the data the asset holds and the processes that depend on it.
- Exposure — whether the vulnerable service is reachable from the internet, from a broad internal network, or only from a restricted segment.
Environmental CVSS metrics are the formal place to express these, but most teams find it simpler to keep criticality and exposure as separate fields in the inventory and apply them in the tiering logic.
Building priority tiers
The goal is a small number of tiers — four is common — each with a clear meaning and a matching SLA. The matrix below is an example policy, not a standard; calibrate it against your own remediation capacity.
| Tier | Example criteria | Intent |
|---|---|---|
| P1 — Act | In KEV and (internet-facing or criticality tier 1); or vendor/SOC confirms active exploitation against you | Emergency change; fix or mitigate within days |
| P2 — Attend | In KEV on any other asset; or EPSS ≥ your high threshold with CVSS-B ≥ 7.0 on an exposed or tier-1 asset | Next patch cycle, tracked closely |
| P3 — Track closely | CVSS-B ≥ 7.0 without exploitation signals; or elevated EPSS on low-criticality assets | Standard maintenance window |
| P4 — Track | Everything else | Fix through routine patching and hygiene |
Notice that no tier is defined by base score alone, and that KEV membership always lifts a finding out of the bottom tiers.
A minimal enrichment script
The script below enriches a list of CVEs from your scanner export with KEV membership and EPSS scores, then applies the example tiering above. It is a starting point for a pipeline, not a product: in production you would cache both feeds, batch EPSS requests, and read criticality and exposure from your inventory rather than from the CSV.
import csv
import requests
KEV_URL = "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json"
EPSS_URL = "https://api.first.org/data/v1/epss"
EPSS_HIGH = 0.1 # example threshold: tune against your own backlog
def load_kev() -> dict[str, dict]:
data = requests.get(KEV_URL, timeout=30).json()
return {v["cveID"]: v for v in data["vulnerabilities"]}
def load_epss(cves: list[str]) -> dict[str, float]:
scores: dict[str, float] = {}
for i in range(0, len(cves), 100): # keep query strings short
batch = ",".join(cves[i : i + 100])
rows = requests.get(EPSS_URL, params={"cve": batch}, timeout=30).json()["data"]
scores.update({r["cve"]: float(r["epss"]) for r in rows})
return scores
def tier(f: dict, in_kev: bool, epss: float) -> str:
exposed = f["exposure"] == "internet"
crown_jewel = f["criticality"] == "1"
cvss = float(f["cvss_base"] or 0)
if in_kev and (exposed or crown_jewel):
return "P1"
if in_kev or (epss >= EPSS_HIGH and cvss >= 7.0 and (exposed or crown_jewel)):
return "P2"
if cvss >= 7.0 or epss >= EPSS_HIGH:
return "P3"
return "P4"
with open("findings.csv", newline="") as fh:
findings = list(csv.DictReader(fh)) # columns: asset,cve,cvss_base,criticality,exposure
kev = load_kev()
epss = load_epss(sorted({f["cve"] for f in findings}))
for f in findings:
cve = f["cve"]
f["kev"] = cve in kev
f["kev_ransomware"] = kev.get(cve, {}).get("knownRansomwareCampaignUse", "")
f["epss"] = epss.get(cve, 0.0)
f["tier"] = tier(f, f["kev"], f["epss"])
findings.sort(key=lambda f: (f["tier"], -f["epss"]))
Two details matter. CVEs that EPSS has not yet scored come back missing — the script treats them as 0.0, which is a policy decision you should make consciously (a newly published CVE with a public exploit deserves human review, not a silent P4). And the KEV dueDate is worth storing even if you are not a federal agency, because it is a useful external benchmark when you report on remediation metrics.
Pitfalls to avoid
- Hard-coding an EPSS threshold forever. Model versions change score distributions. Re-evaluate thresholds after each major EPSS release.
- Ignoring findings without a CVE. Misconfigurations, end-of-life software and exposed management interfaces have no CVE, EPSS or KEV data. Give them explicit tiering rules so they do not all fall into P4.
- Trusting a single severity source. NVD has had a well-publicized enrichment backlog since early 2024, so many recent CVEs lack an NVD score for weeks. Fall back to the CNA's own score, CISA's Vulnrichment data, or the vendor advisory.
- Letting vendor severity override your context. A vendor's "Critical" is their assessment of their product, not of your deployment.
- Recomputing tiers without recomputing SLAs. If a finding moves from P3 to P1 because it was added to KEV, its SLA clock should reflect that — see the guide on SLAs for clock rules.
A 30-day starting plan
- Week 1: Export open findings with CVE, base score, asset, and whatever criticality and exposure data you have. Measure how many are High or Critical today.
- Week 2: Join the export with KEV and EPSS. Count how many open findings are in KEV and on which assets — this list is your first P1/P2 queue.
- Week 3: Agree the tier definitions and SLAs with IT operations and application owners. Document the criteria in the same place as the SLA policy.
- Week 4: Automate the enrichment daily, route P1 and P2 findings to owners through your remediation workflow, and start tracking time-to-remediate per tier.
Once tiers are running, consider formalizing the decision logic with SSVC, which replaces thresholds with an explicit, auditable decision tree.