Vulnerability intelligence
Reading a CVE: What a CVE Record Tells You and What It Does Not
A CVE record names the flaw and points to the vendor, and the vendor advisory sets the fixed version.
Rank CVEs with CISA's KEV catalog and FIRST's EPSS scores next to CVSS, then build a priority table from both free feeds.
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.
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.
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 vulnerabilityNamedateAdded, the day CISA added the CVEdueDate, the deadline CISA setrequiredAction, usually a vendor fix or a mitigationknownRansomwareCampaignUse, which is Known or Unknownnotes and cwesOf 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.
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:
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, 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:
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.
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.
You need:
www.cisa.gov, api.first.org and services.nvd.nist.gov.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.
curl -s https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json -o kev.json
ls -l kev.jsonThe 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.
jq '.vulnerabilities | length' kev.jsonOn the reference machine this returned command not found, because jq was missing. The Python equivalent returned:
python -c "import json; d=json.load(open('kev.json')); print(len(d['vulnerabilities']))"1769The top-level count field should show the same number.
jq '.vulnerabilities[] | select(.cveID=="CVE-2021-44228") | {cveID, dateAdded, dueDate, requiredAction}' kev.jsonThe Python equivalent printed this, with requiredAction shortened here:
{
"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.
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:
{"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.
Save this as priority.py. It needs only the Python standard library.
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 keyRun it on six CVEs: two from the catalog examples and four chosen to contrast with them.
python priority.py kev.json CVE-2021-44228 CVE-2023-4863 CVE-2024-3400 CVE-2023-44487 CVE-2023-38545 CVE-2024-3094Output from the reference run:
| 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:
find for log4j-core*.jar under the user profile and the repository returned nothing. Result: no.libwebp* under /mingw64, /usr and the user profile returned nothing. Result: no.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.xz --version reports 5.6.4. NVD lists 5.6.0 and 5.6.1 as affected. Result: no.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.
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.
https://www.first.org/epss/how-it-works, which covers calibration and limits.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.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.
Vulnerability intelligence
A CVE record names the flaw and points to the vendor, and the vendor advisory sets the fixed version.
Vulnerability intelligence
What a CVSS score like 9.8 means, how v4.0 and v3.1 vector strings are built, and how to decode and recalculate one by hand.