Safeguard
Vulnerability Analysis•

EPSS Scores for KEV vs Non-KEV CVEs: The 17 October 2026 Snapshot

We split all 390,724 EPSS scores from 17 Oct 2026 by KEV status. Median for KEV CVEs: 0.49. For everything else: 0.0064. 460 KEV CVEs still score below 0.1.

Safeguard Security Team
6 min read

EPSS published scores for 390,724 CVEs on 17 October 2026. We split them into the 1,747 that are in CISA's KEV catalog and the 388,977 that are not, then compared the distributions.

The gap is large, as you'd expect: the median KEV CVE scores 0.49083 and the median non-KEV CVE scores 0.00643. The more useful finding is the overlap. 460 KEV CVEs score below 0.1, and 3,463 non-KEV CVEs score 0.5 or higher.

How we counted

Two files: the EPSS daily scores for 2026-10-17 (header: model_version:v2026.06.15, score_date:2026-10-17T12:00:28Z) and KEV catalog version 2026.10.16, both downloaded on 18 October 2026.

curl -sS -o kev.json \
  https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json
curl -sSL -o epss_2026-10-17.csv.gz \
  https://epss.empiricalsecurity.com/epss_scores-2026-10-17.csv.gz
import json, gzip, csv, collections

kev = {v["cveID"] for v in json.load(open("kev.json"))["vulnerabilities"]}
with gzip.open("epss_2026-10-17.csv.gz", "rt") as f:
    print(f.readline().strip())
    s = {r["cve"]: float(r["epss"]) for r in csv.DictReader(f)}

def bucket(x):
    if x < 0.001: return "a. below 0.001"
    if x < 0.01:  return "b. 0.001 to 0.01"
    if x < 0.1:   return "c. 0.01 to 0.1"
    if x < 0.5:   return "d. 0.1 to 0.5"
    return "e. 0.5 or higher"

k = [x for c, x in s.items() if c in kev]
n = [x for c, x in s.items() if c not in kev]
print(len(s), "scored;", len(k), "KEV;", len(n), "not KEV")
ck, cn = collections.Counter(map(bucket, k)), collections.Counter(map(bucket, n))
for b in sorted(set(ck) | set(cn)):
    print(b, ck[b], round(100 * ck[b] / len(k), 1), cn[b], round(100 * cn[b] / len(n), 2))

med = lambda xs: sorted(xs)[len(xs) // 2]
print("median KEV", med(k), "median not KEV", med(n))
for t in (0.1, 0.5, 0.9):
    hi = [c for c, x in s.items() if x >= t]
    print(t, len(hi), sum(c in kev for c in hi))
top = sorted(s, key=s.get, reverse=True)
print("top 100 in KEV:", sum(c in kev for c in top[:100]),
      "top 1000 in KEV:", sum(c in kev for c in top[:1000]))

KEV had 1,748 entries. One, CVE-2026-21589, had no EPSS score on 17 October; its CVE record was rejected on 16 October. For the top 100 and top 1,000 counts, ties are broken by order in the file.

What's affected

Distribution of EPSS scores on 17 October 2026:

EPSS scoreKEV CVEsShare of KEVNon-KEV CVEsShare of non-KEV
Below 0.00100.0%3,0760.79%
0.001 to 0.01613.5%239,73661.63%
0.01 to 0.139922.8%130,13333.46%
0.1 to 0.541723.9%12,5693.23%
0.5 or higher87049.8%3,4630.89%
Total1,747388,977

Looking from the other direction, at how much of each high score band is KEV:

ThresholdCVEs at or aboveOf which in KEV
0.117,3191,287
0.54,333870
0.9804470

Of the 100 highest scored CVEs, 94 are in KEV. Of the top 1,000, 527 are.

How to check if you're exposed

The question for your program is which of these two failure modes it is more exposed to. Export the open CVE IDs from your scanner, one per line, into my_cves.txt, then:

python3 -I -c '
import json, gzip, csv
kev = {v["cveID"] for v in json.load(open("kev.json"))["vulnerabilities"]}
with gzip.open("epss_2026-10-17.csv.gz", "rt") as f:
    f.readline()
    s = {r["cve"]: float(r["epss"]) for r in csv.DictReader(f)}
mine = set(open("my_cves.txt").read().split())
low_kev = [c for c in mine if c in kev and s.get(c, 0) < 0.1]
high_not = [c for c in mine if c not in kev and s.get(c, 0) >= 0.5]
print("KEV but EPSS below 0.1:", len(low_kev), sorted(low_kev)[:20])
print("Not KEV, EPSS 0.5 or higher:", len(high_not), sorted(high_not)[:20])
'

The first list is the one a pure EPSS threshold would deprioritize. The second is the one a pure KEV policy would leave alone.

What to do now

  1. Use both signals with clear roles. KEV says exploitation has been observed. EPSS estimates the chance of exploitation activity in the next 30 days. Neither replaces the other.
  2. Don't drop KEV CVEs on low EPSS. 460 KEV CVEs score below 0.1 today. A threshold policy would push every one of them down the list.
  3. Look hard at high EPSS non-KEV CVEs. 3,463 CVEs score 0.5 or higher without a KEV listing. Those are candidates for urgent work even without a catalog entry.
  4. Pick thresholds from your own capacity. If your team can close 200 findings a week, find the EPSS cut that yields about that many from your backlog, then review it monthly.
  5. Add reachability before effort. A KEV or high EPSS CVE in a function your code never calls can wait behind one you call on every request.

Why it matters

The two lists agree at the extreme top: 94 of the top 100 EPSS scores are KEV CVEs. Below that, they diverge fast. Just over half of KEV CVEs (877 of 1,747) score below 0.5, and 3,463 of the 4,333 CVEs scoring 0.5 or higher are not in KEV.

That divergence is expected. KEV is a curated list of confirmed exploitation, and CISA adds an entry only when it has evidence and clear remediation guidance. EPSS models the likelihood of activity across every published CVE. Treating them as the same thing on different scales is where prioritization goes wrong.

Try it yourself

Run the snippet above against your own backlog. Count each list, then pick one CVE from each and look up whether the vulnerable function is reachable from your code. The demo below shows how reachability changes a long list of findings into a short one.

Click through it at your own pace, or open it full screen.

Quick check

What were the median EPSS scores for KEV and non-KEV CVEs on 17 October 2026?

0.49083 for the 1,747 scored KEV CVEs and 0.00643 for the 388,977 non-KEV CVEs.

How many KEV CVEs had an EPSS score below 0.1?

460: 61 between 0.001 and 0.01, and 399 between 0.01 and 0.1.

Of the CVEs scoring 0.9 or higher, how many are in KEV?

470 of 804.

Sources

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.