50,000 CVEs a year, and under 1% on the exploited list: the numbers

Short answer: the National Vulnerability Database published 49,972 CVE records dated 2025, up from 19,222 in 2020, a 2.6-fold rise in five years. Over the same period, the CISA Known Exploited Vulnerabilities catalog (KEV) grew by a few hundred entries a year. Most CVEs are never known to be exploited. KEV is a floor, not a measure of real exploitation, so this is evidence for prioritizing, not for ignoring.

CVE fatigue is easy to assert and rarely measured. We pulled the counts from the primary sources on 2026-11-01 and give the script so you can check them. Everything here is a count of records, which says little about risk by itself.

How many CVEs are published

Counts of CVE records by NVD publication date, from the NVD CVE API (totalResults), queried 2026-11-01:

Year CVEs published (NVD) Change on prior year
2019 18,938
2020 19,222 +1.5%
2021 21,950 +14.2%
2022 26,431 +20.4%
2023 30,949 +17.1%
2024 40,704 +31.5%
2025 49,972 +22.8%

Two cautions. First, these are NVD’s counts by publication date in NVD. The CVE Program’s own counts by other dates differ somewhat (figures such as 48,185 for 2025 circulate from CVE-based tallies), and the two can shift as records are added or rejected, so quote a source and a date. Second, a rise in records is not a rise in danger: more CNAs, more open-source reporting, and more automated and AI-assisted discovery all increase counts.

How many are known to be exploited

CISA’s KEV catalog lists vulnerabilities with evidence of active exploitation. Its JSON feed (released 2026-10-30, catalog version 2026.10.30) had 1,766 entries. By the year each entry was added:

Year added Entries added
2021 311 (includes the initial backfill when the catalog launched)
2022 555
2023 187
2024 186
2025 245
2026 (to 30 Oct) 282

Of the 1,766 entries, 361 are flagged as known to be used in ransomware campaigns (knownRansomwareCampaignUse = “Known” in the feed).

For a rough ratio, count KEV entries by the year in their CVE identifier and compare with NVD’s count for that year. This is an approximation because the CVE ID year is the reservation year, not the NVD publication year, and recent years are incomplete (exploitation is often recognized months or years later):

CVE ID year In KEV today NVD published that year Approximate share
2020 146 19,222 0.8%
2021 216 21,950 1.0%
2022 132 26,431 0.5%
2023 166 30,949 0.5%
2024 164 40,704 0.4%
2025 189 49,972 0.4%

So on the best public list of confirmed exploitation, the share is on the order of one percent or less per year. Read that carefully: KEV only contains vulnerabilities CISA has evidence of and chose to catalog (each entry carries a required action and a due date), so it lags reality. Exploitation of CVEs that never reach KEV certainly happens. EPSS, a model from FIRST that estimates the probability a CVE is exploited in the next 30 days, exists because KEV alone is incomplete (FIRST).

What the numbers do and don’t say

They support:

  • The volume of advisories grew much faster than most teams’ capacity to read them.
  • Treating every “critical” as an emergency does not scale; a prioritization step is necessary (a method, for Dependabot alerts).
  • “Is it in KEV?” is a cheap, strong first filter. An entry there should jump the queue.

They don’t support:

  • “Most CVEs don’t matter, so skip them.” Counts are across all software; the question is what you run, and one exploited bug in your edge service is the whole story.
  • A precise “percent exploited.” No public data gives that.
  • Comparing across years without noting changes in how CVEs are assigned and reported.

Reproduce the counts

NVD’s API limits a date-range query to 120 days, so a full year needs several requests (a whole-year query returned HTTP 404 when we tried), and the public API is rate limited. This tested script chunks each year into 90-day ranges:

import json, time, datetime, urllib.request

def count(a, b):
    u = ("https://services.nvd.nist.gov/rest/json/cves/2.0?resultsPerPage=1"
         f"&pubStartDate={a}T00:00:00.000&pubEndDate={b}T23:59:59.999")
    r = urllib.request.Request(u, headers={"User-Agent": "cve-count-demo"})
    return json.load(urllib.request.urlopen(r, timeout=60))["totalResults"]

for y in range(2019, 2026):
    total, d = 0, datetime.date(y, 1, 1)
    while d.year == y:
        e = min(d + datetime.timedelta(days=89), datetime.date(y, 12, 31))
        total += count(d.isoformat(), e.isoformat())
        d = e + datetime.timedelta(days=1)
        time.sleep(7)          # be polite to the public API
    print(y, total)

And the KEV counts, from the feed (https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json):

import json, collections
d = json.load(open("known_exploited_vulnerabilities.json"))
print(d["count"], d["dateReleased"])
print(sorted(collections.Counter(v["dateAdded"][:4] for v in d["vulnerabilities"]).items()))
print(sorted(collections.Counter(v["cveID"].split("-")[1] for v in d["vulnerabilities"]).items()))

Your numbers will differ slightly from ours as NVD updates and as KEV grows; that is expected.

What to do with it

Filter first on exploited (KEV, vendor statements), then on whether you run the affected code and whether it is reachable, then fix. For the “do I run it” question, monitor what is actually installed in running services rather than what a manifest says (how).

Limits

  • Counts come from NVD and the KEV feed on 2026-11-01; both change daily.
  • “Published” counts include records NVD later marks rejected or awaiting analysis; we did not filter them.
  • KEV share by CVE ID year is an approximation, as explained.

Facts last verified . Spotted an error? Tell us.

New here? Start here for the guides in a sensible order.