Skip to content
New

DeepSeek V4.1 Flash free for 7 days: 10M tokens a day after any top-up. Until Nov 30.

How it works

CISA KEV and EPSS: Prioritising by Exploitation Evidence

Rank CVEs with CISA's KEV catalog and FIRST's EPSS scores next to CVSS, then build a priority table from both free feeds.

Beginner11 min readBy NotHoly

Your scanner returns 400 findings and you have one week. CVSS can sort them by severity, but a 9.8 that nobody uses can wait, while a 7.5 that attackers already exploit should not. Two free sources tell you which flaws are used in practice. CISA's Known Exploited Vulnerabilities (KEV) catalog lists flaws with confirmed exploitation. FIRST's Exploit Prediction Scoring System (EPSS) estimates how likely each flaw is to be exploited in the next 30 days.

This article explains both, shows how they sit next to CVSS, and then walks through a lab where you query the two feeds for the same CVEs and build a small priority table. The thing to keep from it: severity, exploitation evidence and your own exposure decide the order of patching together. Each one alone gives a wrong order.

If CVSS is new to you, read what CVSS is first. For the difference between a vulnerability, a threat and an exploit, see what a vulnerability is.

What CVSS tells you and what it leaves out

The Common Vulnerability Scoring System rates a flaw from its vector: how an attacker reaches it, how much effort it takes, whether the attacker needs privileges or a user's action, and what the flaw can damage. The base score is a number from 0 to 10 built from those properties.

The base score answers one question: how bad could this be if someone used it? It does not answer whether anyone is using it. CVSS has temporal metrics that can include exploit maturity, but most dashboards show the base score first, and that number does not change after the CVE is published.

Two CVEs from the lab below show the gap. CVE-2021-44228 (Log4Shell) has a base score of 10.0 in the NVD. CVE-2023-44487 (HTTP/2 Rapid Reset) has 7.5. A score-only sort puts Log4Shell first, which is correct. The problem shows up with a backlog of forty flaws rated 9.8 and no sign that any of them has been used. A score gives you a starting point. It does not give you an order.

CISA KEV: confirmed exploitation

The KEV catalog is CISA's list of vulnerabilities that have been exploited in the wild. CISA says it maintains the authoritative source for that list, and it recommends that organizations use the catalog as an input to their prioritization framework. Inclusion depends on reliable evidence that the flaw is being actively used. Entries arrive as that evidence arrives.

The catalog file I downloaded for this article (catalog version 2026.11.06, released 2026-11-06) has 1,769 entries. Each entry carries these fields:

  • cveID, vendorProject, product and vulnerabilityName
  • dateAdded, the day CISA added the CVE
  • dueDate, the deadline CISA set
  • requiredAction, usually a vendor fix or a mitigation
  • knownRansomwareCampaignUse, which is Known or Unknown
  • notes and cwes

Of the 1,769 entries, 361 are marked Known for ransomware use. 285 were added in 2026 up to the release date.

Two limits matter. First, the catalog is not complete. Exploitation often starts before CISA confirms it, and some exploited flaws never get an entry. A CVE missing from KEV is unconfirmed, which is a different thing from safe. Second, KEV does not say whether your copy of a product is affected. That answer comes from your own inventory.

Where the deadlines come from

Most people know the KEV deadlines from BOD 22-01, a binding operational directive CISA issued on November 3, 2021. It told U.S. federal agencies to remediate KEV entries within six months for CVE IDs assigned before 2021, and within two weeks for all other vulnerabilities. The dueDate field still reflects that model in older entries.

BOD 22-01 is no longer the rule. Its page on cisa.gov now says it has been revoked and superseded by BOD 26-04, which CISA lists under June 10, 2026. BOD 26-04 sets remediation timelines from four inputs:

  • KEV status: is the CVE in the catalog?
  • Asset exposure: can unauthenticated users on public networks reach the asset?
  • Exploit automation: can an adversary automate every step of the exploit?
  • Technical impact: how much control does a successful exploit give?

Table 1 in BOD 26-04 holds the timelines themselves. The directive counts calendar days, and it defines "& forensic triage" entries, which must be remediated within three days and then checked for signs of compromise. Read the table on the directive page for the exact values. Do not copy numbers from this article or from older blog posts.

The catalog shows both eras. In the file I downloaded, the gap between dateAdded and dueDate is 21 days for 1,025 entries, 14 days for 286 entries, and about six months (181 or 182 days) for 249 entries. Nearly all of the six-month entries have CVE IDs from 2020 or earlier, which matches the BOD 22-01 rule. The 21-day and 14-day gaps are dates CISA set per entry, so they do not reflect one fixed rule. 152 entries mention BOD 26-04 in requiredAction or notes.

FIRST EPSS: likelihood over the next 30 days

FIRST, the Forum of Incident Response and Security Teams, maintains EPSS. FIRST describes it as a machine-learning model that estimates the probability that a published CVE will be exploited in the wild in the next 30 days. FIRST publishes the score every day for every CVE. The data is free and needs no registration.

Each CVE gets two numbers:

  • The score is a probability between 0 and 1. A score of 0.05 means a 5% chance of exploitation within 30 days. FIRST says the model is calibrated so the score matches that rate.
  • The percentile is the rank. A percentile of 0.99 means the CVE scores higher than 99% of the CVEs scored that day.

Most scores sit close to zero, because most vulnerabilities are never exploited. That is why the percentile is often easier to read than the raw score. A score of 0.909 looks high on its own, and a percentile of 0.998 tells you how it compares with the rest of the list that day.

FIRST says the model draws on about 2,850 features from five categories (exploit code, chatter, CVSS metrics, age and reference volume, among others), and that it learns from observed exploitation activity supplied by partners. The scores move when new signals arrive. The API can return a 30-day history for one CVE, which helps when you need to explain why a score rose.

EPSS has a clear limit. It predicts exploitation activity. It says nothing about how bad a flaw is, and nothing about whether your systems run the affected code. Treat a high score as a reason to look at the asset, and check the asset before you assume it is at risk.

How they fit with CVSS

Each source answers a different question:

Source Question it answers Form of the answer
CVSS base score How bad would it be if someone used it? Fixed severity, 0 to 10
KEV Has CISA confirmed exploitation? Yes or no, with dates
EPSS How likely is exploitation in the next 30 days? Probability and percentile, updated daily
Your inventory Is the flaw in my systems, and can it be reached? Yes or no, from your own data

Patch order comes from combining the four. Here is a starting point, not a law. Confirmed and reachable comes first. Next come confirmed but unreachable, and unconfirmed with high EPSS and reachable. The rest follow by CVSS and by the value of the asset. BOD 26-04 combines the same kinds of input, so the federal rules follow the same logic.

Hands-on lab: build a priority table

You need:

  • A Bash shell. The reference run used Git Bash on Windows 11, with curl 8.12.1 and Python 3.12.
  • Internet access to www.cisa.gov, api.first.org and services.nvd.nist.gov.
  • jq, optional. jq was not installed on the reference machine, so each jq command below has a Python 3.12 equivalent, and the outputs shown come from the Python commands.

These steps only read public catalogs. None of them scans a host. If you later check the exposure column against live services, do that only on systems you own or have written permission to test.

Step 1: download the KEV catalog

bash
curl -s https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json -o kev.json
ls -l kev.json

The download is silent. On the reference run the file was 1,832,349 bytes. Open the top of the file and check catalogVersion and dateReleased before you trust the counts.

Step 2: count the entries

bash
jq '.vulnerabilities | length' kev.json

On the reference machine this returned command not found, because jq was missing. The Python equivalent returned:

bash
python -c "import json; d=json.load(open('kev.json')); print(len(d['vulnerabilities']))"
text
1769

The top-level count field should show the same number.

Step 3: look up one CVE in KEV

bash
jq '.vulnerabilities[] | select(.cveID=="CVE-2021-44228") | {cveID, dateAdded, dueDate, requiredAction}' kev.json

The Python equivalent printed this, with requiredAction shortened here:

json
{
  "cveID": "CVE-2021-44228",
  "dateAdded": "2021-12-10",
  "dueDate": "2021-12-24",
  "requiredAction": "For all affected software assets for which updates exist, the only acceptable remediation actions are: 1) Apply updates; OR 2) remove affected assets from agency networks. ..."
}

Run the same query for CVE-2023-4863 and you get dateAdded 2023-09-13 and dueDate 2023-10-04. The dates are the reason to keep the catalog file: they record when CISA acted, not only whether it did.

Step 4: ask EPSS about the same CVEs

bash
curl -s 'https://api.first.org/data/v1/epss?cve=CVE-2021-44228,CVE-2023-4863' | jq '.data'

The raw response from the reference run:

json
{"status":"OK","status-code":200,"version":"1.0","access":"public","total":2,"offset":0,"limit":100,"data":[{"cve":"CVE-2023-4863","epss":"0.999790000","percentile":"0.999800000","date":"2026-11-07"},{"cve":"CVE-2021-44228","epss":"0.999990000","percentile":"1.000000000","date":"2026-11-07"}]}

Two details matter in code. The rows come back in a different order from the request, so match on cve and never rely on position. The values are strings, so convert them to numbers before you sort or compare.

Step 5: build the table with a script

Save this as priority.py. It needs only the Python standard library.

python
import json, sys, time, urllib.request

KEV_FILE = sys.argv[1]
CVES = sys.argv[2:]

def get(url):
    with urllib.request.urlopen(url, timeout=60) as r:
        return json.load(r)

kev = {v["cveID"]: v for v in json.load(open(KEV_FILE))["vulnerabilities"]}
epss = {row["cve"]: row for row in get(
    "https://api.first.org/data/v1/epss?cve=" + ",".join(CVES))["data"]}

print("| CVE | CVSS 3.1 base | In KEV | EPSS | Percentile |")
print("|---|---|---|---|---|")
for cve in CVES:
    nvd = get(f"https://services.nvd.nist.gov/rest/json/cves/2.0?cveId={cve}")
    metrics = nvd["vulnerabilities"][0]["cve"].get("metrics", {})
    cvss = metrics.get("cvssMetricV31", [{}])[0].get("cvssData", {}).get("baseScore", "n/a")
    e = epss.get(cve, {})
    print(f"| {cve} | {cvss} | {'yes' if cve in kev else 'no'} | "
          f"{float(e.get('epss', 0)):.5f} | {float(e.get('percentile', 0)):.5f} |")
    time.sleep(7)  # the NVD API rate-limits requests without a key

Run it on six CVEs: two from the catalog examples and four chosen to contrast with them.

bash
python priority.py kev.json CVE-2021-44228 CVE-2023-4863 CVE-2024-3400 CVE-2023-44487 CVE-2023-38545 CVE-2024-3094

Output from the reference run:

text
| CVE | CVSS 3.1 base | In KEV | EPSS | Percentile |
|---|---|---|---|---|
| CVE-2021-44228 | 10.0 | yes | 0.99999 | 1.00000 |
| CVE-2023-4863 | 8.8 | yes | 0.99979 | 0.99980 |
| CVE-2024-3400 | 10.0 | yes | 0.99999 | 1.00000 |
| CVE-2023-44487 | 7.5 | yes | 0.99999 | 0.99998 |
| CVE-2023-38545 | 9.8 | no | 0.78689 | 0.99591 |
| CVE-2024-3094 | 10.0 | no | 0.90900 | 0.99808 |

The script does not fill the exposure column, because only you know your inventory. Here is how the reference run filled it, and what each check covered:

  • CVE-2021-44228: find for log4j-core*.jar under the user profile and the repository returned nothing. Result: no.
  • CVE-2023-4863: a search for libwebp* under /mingw64, /usr and the user profile returned nothing. Result: no.
  • CVE-2023-38545: curl --version reports 8.12.1. The NVD range for affected libcurl is 7.69.0 up to, but not including, 8.4.0. Result: no, for upstream libcurl. See the caveat below.
  • CVE-2024-3094: xz --version reports 5.6.4. NVD lists 5.6.0 and 5.6.1 as affected. Result: no.
  • CVE-2024-3400 and CVE-2023-44487: not checked on this machine.

The exposure column is the one people get wrong most often. Linux distributions and vendors often backport fixes, so a version number alone can mislead. Check the vendor's advisory for your build before you close a row as safe.

Step 6: read the table

Put the rows in patch order using the rule from the section above. If CVE-2021-44228, CVE-2023-4863 or CVE-2024-3400 were reachable on your network, they would be the first patches of the week, whatever the rest of the list says.

CVE-2023-44487 has a lower CVSS score than CVE-2023-38545, but it is confirmed and its EPSS score is 0.99999. In a list where both are reachable, it goes first. The 9.8 that CISA has not confirmed moves down.

CVE-2024-3094 is the case that breaks a simple rule. CISA's data does not list it, yet its EPSS score is 0.909 and its CVSS score is 10.0. On this machine the version is not affected, so the row closes. On a host running xz 5.6.1, the same row goes to the top, because the score says exploitation activity is very likely and the flaw is critical.

Common mistakes

  • Sorting by CVSS alone. The score measures impact, not use. Put the KEV and EPSS columns beside it.
  • Treating KEV as the full list. A missing entry means CISA has not confirmed exploitation. It does not mean the flaw is harmless.
  • Reading EPSS as impact or as proof. EPSS estimates exploitation activity. It does not tell you the flaw is on your network.
  • Copying federal deadlines. BOD 22-01 is revoked. BOD 26-04 applies to federal agencies. Outside that scope its timelines are a model, and you should set your own from your risk.
  • Bulk-querying the API. FIRST built the EPSS API for lookups. For the full dataset, download the daily CSV.
  • Forgetting the date. EPSS scores change daily, and KEV grows every week. Write the date next to every table.
  • Trusting version numbers without the vendor advisory. Backports and build options change what is affected.
  • Skipping the exposure column. A table without asset data is an opinion about the flaws, not a priority list for your systems.

How to go further

  • Read the EPSS methodology page at https://www.first.org/epss/how-it-works, which covers calibration and limits.
  • Use the daily EPSS CSV at https://epss.empiricalsecurity.com/epss_scores-current.csv.gz and join it to the KEV file on cveID. That checks hundreds of CVEs in one pass.
  • Read BOD 26-04 and its Table 1. The directive names CISA's Vulnrichment program as a source of KEV status, exploit automation and technical impact for CVEs.
  • Add two columns from your own records: whether the asset is internet-facing, and what it is worth to you. Those two columns usually reorder more of the list than the scores do.

Sources

The data in this article was read on 2026-11-08 from the sources listed in the article metadata. KEV counts and EPSS scores change over time, so check the date on your own run.

Sources

  1. CISA Known Exploited Vulnerabilities Catalog
  2. CISA KEV JSON feed
  3. CISA BOD 22-01: Reducing the Significant Risk of Known Exploited Vulnerabilities (revoked)
  4. CISA BOD 26-04: Prioritizing Security Updates Based on Risk
  5. FIRST Exploit Prediction Scoring System (EPSS)
  6. FIRST EPSS: How It Works
  7. FIRST EPSS: Get the Data
  8. FIRST EPSS API reference
  9. NIST National Vulnerability Database

More in Vulnerability intelligence