Loading...
Skip to main content

The 6% Problem — What 12 Months of KEV Data Says About CVE Exploitation

Sudhir P. · Founder, GeekWala
· Last updated August 10, 2026

Bottom line up front

Why we ran this

Every security team that has wired npm audit into CI has lived the same Tuesday morning: the pipeline turns red, a dependency bot files seventeen PRs, and the on-call engineer has to decide which of those seventeen actually matters before the standup meeting. Severity alone does not answer the question. A High-severity CVE without a public exploit is not the same emergency as a Medium-severity CVE that showed up on the CISA Known Exploited Vulnerabilities list last Friday.

We kept hearing the same operator complaint across our user base: "We don't know which vulnerabilities to prioritize, because severity is everywhere and exploitation signal is almost nowhere." So we ran an audit. Twelve months of published CVEs across eight package ecosystems, cross-joined with the EPSS exploit-prediction scores and the CISA KEV catalog. The dataset that produced every number below is linked at the bottom, with the full methodology documented inside it — see Reproducibility.

Headline findings

  • Q1 — 0.19% of newly published CVEs landed on CISA KEV during the 12-month window (jump).
  • Q2 — Median time from CVE publication to KEV listing was 82 days, with p95 at roughly 1,604 days (jump).
  • Q3 — PyPI produced the largest share of KEV entries among tracked package ecosystems (jump).
  • Q4 — At EPSS ≥ 0.10, efficiency (precision) was 16.7% and coverage (recall) was 94.4% against KEV membership (jump).
  • Q5 — Monthly KEV additions increased over the window (jump).

Finding 1 — The KEV rate

The single most useful filter you can apply to a CVE feed is "is this on KEV." Of the 9,427 CVEs newly published across these 8 ecosystems in the 12-month window, only 0.19% (18 CVEs) have — as of this writing — reached the CISA Known Exploited Vulnerabilities catalog. That is not a filter that rejects half your tickets. It is a filter that keeps roughly one ticket in 524 and rejects the other 523. (This is a floor, not a final number: CVEs published near the end of the window have had the least time to be observed as exploited — see Limitations.)

There is a second, equally useful cut of the same catalog: of the 269 KEV entries CISA added in the window across every vendor and product, only 27 (10.0%) touch a package in any of these 8 ecosystems at all. Most KEV coverage is network appliances, hypervisors, and operating systems — not your dependency tree. That 10% is the number a dependency-scanning team should actually anchor on; it rarely gets quoted on its own.

The practical consequence: an npm-audit-style pipeline that fires an alert on every published CVE is generating far more noise than it has to relative to confirmed exploitation. Teams that wire KEV membership into their alerting (blocking deploys on KEV, soft-alerting on non-KEV) cut their paging volume without losing coverage of actual exploitation — but should not expect KEV membership itself to be a common event for any single ecosystem's feed.

Finding 2 — Time-to-KEV

Median time from publication to KEV listing was 82 days across the 27 matched entries; the 95th percentile was roughly 1,604 days. That p95 figure is not a typo — it is dominated by three vulnerabilities (a 2018 Jenkins core flaw, a 2021 Grafana path-traversal bug, and a 2021 Adminer SSRF) that CISA added to KEV in late 2025 after years of quiet existence, almost certainly because of renewed real-world exploitation campaigns rather than anything about the original disclosure. Exclude those three and the remaining 24 range from same-day to 329 days.

Operator takeaway: a weekly scan against the current KEV catalog catches the vast majority of exploitation signal in roughly the same real-time window as a daily scan for recently-disclosed CVEs. But the long tail is real and it is not just "patches already shipped" — it also includes old, previously-quiet vulnerabilities in unmaintained or legacy deployments getting re-weaponized years later. A KEV feed needs to be rechecked against your full historical dependency graph, not just new CVEs.

Finding 3 — Ecosystem shares

PyPI produced the largest share of KEV listings over the window (7 of 27, 26%), narrowly ahead of Packagist and npm (6 each, 22%), Maven (5, 19%), and Go (3, 11%). crates.io, RubyGems, and NuGet had zero matched KEV entries in the window. At n=27 we cannot tell whether that zero reflects genuinely lower attacker interest in Rust, Ruby, and .NET packages or is simply what a sample this small looks like — both are plausible, and the dataset does not let us separate them.

That is partly a raw-volume effect (larger ecosystems publish more CVEs, so produce more KEV entries in absolute terms) and partly a targeting effect (attackers follow the install base) — this dataset cannot cleanly separate the two at this sample size either. The Limitations section below covers why per-ecosystem percentages here should be read as counts first, rates second.

Finding 4 — EPSS precision

EPSS (the Exploit Prediction Scoring System) assigns every CVE a 0-to-1 probability of being exploited in the next 30 days. The natural question: how well does that score predict KEV membership?

Rather than a literal top-10 precision/recall cutoff (with a true-positive population of only 18 KEV-matched CVEs in the scored population, a rank-10 cutoff is close to meaningless by construction), we report efficiency (precision) and coverage (recall) as a trade-off curve across EPSS score thresholds, mirroring FIRST's own published EPSS validation methodology. At EPSS ≥ 0.10, efficiency was 16.7% (17 of 102 CVEs flagged at that threshold were KEV-listed) and coverage was 94.4% (17 of the 18 KEV-listed CVEs in the population scored ≥ 0.10). At EPSS ≥ 0.50, efficiency rises to 60% while coverage falls to 83.3%. Selecting just the top 5% of scored CVEs by rank would have caught all 18. This curve uses each CVE's current score against its KEV status today, not a blind forecast — see Limitations for why that matters and how it differs from the lead-time finding below.

Finding 5 — Monthly trend

Monthly KEV additions among the 27 matched entries rose across the window: 10 entries in the first half (September 2025–January 2026) versus 17 in the second half (February–August 2026). The series is short and noisy month to month (a single month ranged from 1 to 5 entries), so this should be read as a directional signal, not a stable rate.

Finding 6 — How much warning EPSS actually gave, and where it missed

This is the finding the rest of the report is building toward. For each of the 27 KEV-matched CVEs, we pulled the historical EPSS score on every day between its CVE-publish date and its KEV-add date (444 date-batched API calls, 0 failures) and checked whether the score ever reached 0.10 before CISA acted.

EPSS gave advance warning in 14 of 27 cases (51.9%), with a median lead time of 165 days before KEV listing (124 days if the three legacy-re-exploitation outliers from Finding 2 are excluded). In the best case, EPSS had flagged a vulnerability at high risk more than a year before CISA confirmed exploitation.

EPSS gave zero advance warning in the other 13 cases (48.1%). This is the honest number, and it is close to a coin flip. Every one of these was scored below 0.10 — most below 0.01 — right up to the day CISA added it to KEV. The list includes a Magento/Adobe Commerce RCE, a Drupal core vulnerability, two LiteLLM CVEs, and three separate Langflow CVEs. For two of these (an ESLint config package and a TanStack router package), EPSS still has not caught up: current scores remain at 0.04 and 0.02 respectively, months after KEV listing. For the rest, EPSS jumped sharply after the KEV listing itself — evidence that the model is likely picking up the exploitation-confirmation event (or the news cycle around it) rather than having predicted it independently. We are GeekWala, and our own prioritization leans on EPSS; publishing this miss rate, not just the hit rate, is the point of running this audit on ourselves.

A patch already existed on the day CISA added the CVE to KEV in 18 of 19 determinable cases (94.7%). Fix-availability could not be determined for the remaining 8 — Jenkins, GeoServer, and XWiki publish outside Maven Central, Roundcube's old maintenance-branch tags are missing from Packagist's metadata, and two CVEs (Magento, Gogs) have no fixed version recorded in OSV at all as of this writing. Where determinable, the one exception (a Langflow CVE) had its fix ship 20 days after KEV listing — CISA confirmed exploitation of an unpatched vulnerability. For a fixed 72-hour KEV remediation SLA, a ~95% same-or-earlier-day patch-availability rate means the SLA is achievable for the large majority of cases in this sample, but not universally.

Methodology

The dataset joins four public sources on CVE ID:

  • CISA KEV catalog — the full Known Exploited Vulnerabilities JSON feed (1,660 entries at collection time), filtered to the 269 added in the trailing 12 months. We use dateAdded for time-to-KEV and matched every one of those 269 against OSV, not just the ones published inside the window — a KEV entry can reference a CVE from years earlier.
  • OSV bulk exports — the full per-ecosystem export for each of the 8 tracked ecosystems (storage.googleapis.com/osv-vulnerabilities/<ECOSYSTEM>/all.zip), scanned for CVE aliases and cross-checked against KEV. A separate publish-date filter (preferring NVD's confirmed publication date over OSV's own advisory timestamp) produced the 9,427-CVE population used as the Finding 1 denominator.
  • FIRST EPSS — both the current daily snapshot (epss.empiricalsecurity.com, all ~355,000 scored CVEs, used for Finding 4) and historical scores from the FIRST API's date parameter (batched by calendar date across all 27 matched CVEs, 444 dates, 0 failures — used for the Finding 6 lead-time analysis).
  • Package registries — npm, PyPI, Maven Central, Packagist, and the Go module proxy (with a GitHub Releases fallback), queried directly for the release timestamp of each CVE's earliest recorded fixed version, to determine fix availability at KEV-add time.

CVE IDs are normalised to uppercase throughout. Every endpoint, parameter, and exclusion rule used to build this dataset — plus the full per-CVE table — is documented in data_sources and methodology inside the committed dataset file itself; that file, not a fixed pipeline run, is the reproducibility anchor. Because CISA adds KEV entries and EPSS rescoring both happen daily, re-running these same endpoints on a later date will produce a newer catalog and updated scores — the committed dataset reflects the state of these public feeds as of its generated_at timestamp.

Limitations

  • EPSS crossed model-version boundaries with documented score-shift discontinuities: v1 (2021-04-14), v2 (2022-02-04), v3 (2023-03-07), v4 (2025-03-17), v5 (2026-06-15). Our window straddles the v4→v5 transition, and the three legacy CVEs in the sample have score histories spanning all five model generations. A rising score over any single CVE's history can reflect a model change rather than accumulating real exploitation signal — read lead-time and trend figures as model-version-aware, never as one continuous series.
  • Finding 4's efficiency/coverage curve scores the population with today's EPSS, not the score at any earlier decision point. For CVEs already on KEV, today's score likely already reflects the fact of their exploitation, since EPSS ingests threat-intel and exploit-code signals that build up after a vulnerability starts getting attacked. That makes Finding 4 a retrospective association, not a blind forecast. The genuine forecast question — would EPSS have flagged this before CISA did — is answered separately, and less flatteringly, by the lead-time results in Finding 6.
  • The samples here are small and some figures are incomplete by nature of the sources, not by choice. Only 27 of 269 window KEV entries touch these 8 ecosystems, so per-ecosystem shares (and the zero counts for crates.io, RubyGems, and NuGet) cannot separate genuine attacker disinterest from noise at n=27. The population-based KEV rate (Finding 1) is right-censored — recently published CVEs haven't had time to mature into KEV yet, so 0.19% is a floor. Fix-availability (Finding 6) was only resolvable for 19 of 27 entries: Jenkins, GeoServer, and XWiki publish outside Maven Central, Roundcube's old maintenance-branch tags are missing from Packagist, and one CVE's monorepo package versions didn't line up with OSV's stated fix.

Reproducibility

Every number in this article traces to a row or an aggregate in the committed dataset: dataset.json. Its data_sources and methodology fields specify the exact endpoints, parameters, and normalization rules used to build it — the CISA KEV feed, the OSV bulk exports, the FIRST EPSS API (both current and historical), and the package-registry lookups. Because CISA adds new KEV entries and EPSS rescoring runs daily, a fresh pull from those same endpoints will not reproduce these exact figures bit-for-bit — it will reflect whatever the public feeds say on the day you run it. The committed dataset file is the fixed, citable snapshot behind this article's numbers.

About the author

Sudhir Prakash is the founder of GeekWala, a dependency vulnerability scanner that scans across npm, PyPI, Maven, Packagist, Go, crates.io, RubyGems, and NuGet with EPSS + CISA KEV enrichment built in. This report grew out of a year of watching operators ask the same prioritization question.

Cite this report

Prakash, S. (2026). The 6% Problem — What 12 Months of KEV Data Says About CVE Exploitation. GeekWala Research. Dataset: https://www.geekwala.com/research/kev-exploitation-2026/dataset.json — License: Creative Commons Attribution 4.0 International (CC BY 4.0).

Provenance

Dataset generated 2026-08-10T15:15:44+00:00 covering 2025-04-21 through 2026-04-21 (201169 records). Source hashes:

OSV
sha256:08b4bb91fa68acb1a545e9082c00e33b9daaa79f9bad43c23f57c9fb56b2f4c3
EPSS
sha256:8c9d4426319ec264101457339793a8b517ca7a7e9e074753a815306d3e511d76
KEV
sha256:2a6c54ce139af5a6e926fdcf2c808c945ad113713fb4554b7c8cf87446ea6ec9