CISA wants the CVE Program to move from what it calls a Growth Era to a Quality Era. Its framework whitepaper, published on 22 September, names the success metric that matters most to anyone consuming CVE data: the percentage of CVE records that meet defined quality criteria. It doesn't define those criteria yet. So we measured the basics ourselves across every CVE record published in 2026.
The short version: records are fuller than they were, but most still aren't complete in the structured fields that scanners read. This explainer is for vulnerability management and product security teams that rely on CVE data, and for CNAs that publish it. It covers what the framework says and doesn't say, what 81,803 records from 2026 contain, the gap between a record a person can read and one a tool can use, and a script that checks the records in your own backlog.
The short answer
CISA's "CVE Program: Establishing a Quality Era Framework" (22 September 2026) sets four quality dimensions (program governance, ecosystem participation, data infrastructure and CVE record content) and lists candidate success metrics, but it doesn't set record requirements. Those are promised in later publications. In the 2026 records we checked, the assigning CNA supplied a CVSS score in 80%, a CWE in 76% and a structured upper version bound in 61%. Only 22% had all four of a score, a CWE, an affected version and a tagged patch or advisory link, up from 11% in 2024. CISA's own enrichment raises CVSS coverage to 95%. Until criteria exist, check the fields your process depends on, and for open-source CVEs take version ranges from GitHub's advisory database or OSV rather than the CVE record.
What the framework says
The whitepaper is four pages. It builds on CISA's earlier strategy document, "Strategic Focus: CVE Quality for a Cyber Secure Future", which set six lines of effort, and maps those lines onto four dimensions:
| Dimension | Focus | Example success metrics in the paper |
|---|---|---|
| Program governance | Transparent stewardship and decision-making | Timeliness of decisions, conflicts of interest identified and resolved |
| Ecosystem participation | Broad, active, global CNA and community involvement | Number and diversity of active CNAs, working group participation |
| Data infrastructure | APIs, schemas, validation libraries and cve.org | Uptime, API throughput, validation error frequency |
| CVE record content | Complete, accurate, timely, actionable records | Share of records meeting defined quality criteria, rate of post-publication corrections |
It sets out the pressure clearly. CISA says more than 67,000 new CVEs had been published in 2026 by 18 September, cites a projection of 96,000 for the year, and notes that NIST's NVD reported a 263% increase in CVE submissions between 2020 and 2025. It names AI-enabled discovery as one of the forces behind the rise.
What it doesn't do is just as important. It sets no required fields, no deadlines and no enforcement for CNAs. The next step it announces is a blog series on cve.org about infrastructure and data modernization, and "future CVE Quality Era publications" with detail. For now it's a direction, not a rulebook.
What 2026 CVE records contain
We read every record in the CVE Program's bulk export (the CVEProject/cvelistV5 release of 17 October) and counted records first published in each year. Our 2026 count, 81,803, is higher than CISA's September figure because it runs a month later and includes records with older CVE IDs published this year:
#!/usr/bin/env python3
"""Measure what 2026 CVE records actually contain, from the CVE List bulk export.
Reads cves.zip from a CVEProject/cvelistV5 release and checks, for each record
published in the given year, whether the CNA supplied a CVSS score, a CWE, a
machine-readable affected version range, and a patch or advisory reference, and
whether CISA's ADP container (Vulnrichment) added data.
Usage: cve_record_quality.py cves.zip 2026
"""
import collections, json, sys, zipfile
def has_range(affected):
for a in affected:
for v in a.get("versions", []):
if v.get("status") == "affected" and (v.get("lessThan") or v.get("lessThanOrEqual")
or str(v.get("version", "")).lower() not in ("", "n/a", "0", "*", "-", "unspecified", "all", "unknown")):
return True
return False
def main(path, year):
z = zipfile.ZipFile(path)
c = collections.Counter()
cnas = collections.Counter()
per = collections.defaultdict(list)
for n in z.namelist():
if not n.endswith(".json") or "/delta" in n:
continue
d = json.loads(z.read(n))
meta = d.get("cveMetadata", {})
if not meta.get("datePublished", "").startswith(year):
continue
if meta.get("state") == "REJECTED":
c["rejected"] += 1
continue
cna = d.get("containers", {}).get("cna", {})
adp = d.get("containers", {}).get("adp", [])
c["published"] += 1
cnas[meta.get("assignerShortName", "?")] += 1
cvss = any(k.startswith("cvss") for m in cna.get("metrics", []) for k in m)
cwe = any(x.get("cweId") for p in cna.get("problemTypes", []) for x in p.get("descriptions", []))
rng = has_range(cna.get("affected", []))
tags = {t for r in cna.get("references", []) for t in r.get("tags", [])}
fixref = bool(tags & {"patch", "vendor-advisory", "release-notes"})
cisa = [a for a in adp if a.get("providerMetadata", {}).get("shortName") == "CISA-ADP"]
cisa_cvss = any(k.startswith("cvss") for a in cisa for m in a.get("metrics", []) for k in m)
cisa_cwe = any(x.get("cweId") for a in cisa for p in a.get("problemTypes", []) for x in p.get("descriptions", []))
vers = [v for a in cna.get("affected", []) for v in a.get("versions", []) if v.get("status") == "affected"]
lt = any(v.get("lessThan") or v.get("lessThanOrEqual") for v in vers)
c["cna_lessthan"] += lt
c["range_as_text"] += (not lt) and any("<" in str(v.get("version", "")) for v in vers)
c["legacy_tags_only"] += (not fixref) and any(t.startswith("x_refsource") for t in tags)
per[meta.get("assignerShortName", "?")].append(cvss and cwe and rng and fixref)
c["cna_cvss"] += cvss; c["cna_cwe"] += cwe; c["cna_range"] += rng; c["cna_fixref"] += fixref
c["all_four"] += cvss and cwe and rng and fixref
c["cisa_adp"] += bool(cisa)
c["cvss_any"] += cvss or cisa_cvss; c["cwe_any"] += cwe or cisa_cwe
p = c["published"]
pct = lambda k: f"{c[k]:>6} {100 * c[k] / p:5.1f}%"
print(f"{year} CVE records published: {p} (plus {c['rejected']} rejected), from {len(cnas)} CNAs")
print("CNA supplied a CVSS score ", pct("cna_cvss"))
print("CNA supplied a CWE ", pct("cna_cwe"))
print("CNA supplied an affected range ", pct("cna_range"))
print("CNA tagged a patch/advisory link ", pct("cna_fixref"))
print("Structured upper bound (lessThan*)", pct("cna_lessthan"))
print("Range only as text, e.g. '< 2.8.0'", pct("range_as_text"))
print("Only legacy x_refsource link tags", pct("legacy_tags_only"))
print("CNA supplied all four ", pct("all_four"))
print("CISA ADP container present ", pct("cisa_adp"))
print("CVSS from CNA or CISA ADP ", pct("cvss_any"))
print("CWE from CNA or CISA ADP ", pct("cwe_any"))
big = {k: v for k, v in per.items() if len(v) >= 50}
share = lambda lo, hi: sum(1 for v in big.values() if lo <= sum(v) / len(v) < hi)
print(f"CNAs with 50+ records: {len(big)}; all four in >=90% of records: {share(0.9, 1.01)}, "
f"in under 10%: {share(0, 0.1)}")
top = cnas.most_common(10)
print(f"top 10 CNAs by volume issued {sum(v for _, v in top)} records ({100 * sum(v for _, v in top) / p:.0f}%)")
if __name__ == "__main__":
main(sys.argv[1], sys.argv[2] if len(sys.argv) > 2 else "2026")
$ python3 cve_record_quality.py cves.zip 2026
2026 CVE records published: 81803 (plus 293 rejected), from 392 CNAs
CNA supplied a CVSS score 65272 79.8%
CNA supplied a CWE 62267 76.1%
CNA supplied an affected range 76156 93.1%
CNA tagged a patch/advisory link 24082 29.4%
Structured upper bound (lessThan*) 49576 60.6%
Range only as text, e.g. '< 2.8.0' 11764 14.4%
Only legacy x_refsource link tags 12369 15.1%
CNA supplied all four 18272 22.3%
CISA ADP container present 75427 92.2%
CVSS from CNA or CISA ADP 77383 94.6%
CWE from CNA or CISA ADP 73786 90.2%
CNAs with 50+ records: 107; all four in >=90% of records: 18, in under 10%: 58
top 10 CNAs by volume issued 55716 records (68%)
The same script on earlier years shows the trend:
| CNA-supplied field | 2024 | 2025 | 2026 |
|---|---|---|---|
| CVSS score | 70.0% | 76.0% | 79.8% |
| CWE | 64.2% | 72.2% | 76.1% |
| Affected version | 83.7% | 88.7% | 93.1% |
Structured upper bound (lessThan or lessThanOrEqual) | 56.1% | 59.0% | 60.6% |
| Patch, vendor advisory or release notes link, tagged as such | 13.3% | 12.2% | 29.4% |
| All four of score, CWE, version and tagged link | 10.7% | 10.4% | 22.3% |
Two things stand out. Every field is improving. And the share of records with everything a scanner or triage process needs is still under a quarter. CISA's Authorized Data Publisher container, also called Vulnrichment, fills much of the gap for scores and weaknesses: with it, 94.6% of 2026 records have a CVSS score and 90.2% have a CWE. It mostly adds scores, weaknesses and SSVC decision points, not fix links.
Quality is also concentrated. Of the 107 CNAs that published at least 50 records in 2026, 18 supplied all four fields in at least 90% of their records, and 58 did so in fewer than 10%.
Readable is not the same as machine-readable
The biggest single gap in the 2026 data is about format, not missing facts. In 11,764 records, the affected range appears only as text inside the version field, such as ">= 1.0.0, < 2.8.0", with no structured lessThan value. 98.5% of those come from one CNA, GitHub, which published 11,741 records this year. Many of the same records tag their links with the legacy x_refsource_CONFIRM and x_refsource_MISC labels rather than patch or vendor-advisory.
A person reading the record sees the fixed version. A tool that reads the CVE 5 schema's range fields doesn't. To be fair to GitHub, its own advisory database and the OSV copy of it carry fully structured ranges. The CVE record is the lossy copy. In practice that means for open-source packages, the CVE record isn't the best source of version data, and tools that rely on it alone will miss or misjudge fixes for a large share of this year's CVEs.
This is exactly the kind of thing a "defined quality criteria" metric should catch. A record can be complete in the human sense and still fail the tools that consume it.
Check the records in your backlog
This script takes CVE IDs and reports which of the four fields the CNA left out, plus CISA's SSVC exploitation value where its enrichment has one. It reads from the CVE Services API and uses only the Python standard library:
#!/usr/bin/env python3
"""Check what the CVE records in your backlog actually tell you.
For each CVE ID, fetches the record from the CVE Services API and reports
whether the CNA gave a CVSS score, a CWE, an affected range with a fix
boundary, and a patch or advisory link, plus CISA's SSVC exploitation value if
its ADP container has one. Standard library only.
Usage: cve_record_check.py CVE-ID [CVE-ID...] (or IDs on stdin, one per line)
"""
import json, sys, urllib.request
def fetch(cve):
req = urllib.request.Request(f"https://cveawg.mitre.org/api/cve/{cve}", headers={"User-Agent": "cve-record-check"})
return json.load(urllib.request.urlopen(req, timeout=30))
def check(cve):
d = fetch(cve)
cna = d["containers"]["cna"]
adp = [a for a in d["containers"].get("adp", []) if a.get("providerMetadata", {}).get("shortName") == "CISA-ADP"]
versions = [v for a in cna.get("affected", []) for v in a.get("versions", []) if v.get("status") == "affected"]
tags = {t for r in cna.get("references", []) for t in r.get("tags", [])}
ssvc = next((o.get("Exploitation") for a in adp for m in a.get("metrics", [])
for o in m.get("other", {}).get("content", {}).get("options", []) if "Exploitation" in o), None)
missing = [name for name, ok in [
("CVSS", any(k.startswith("cvss") for m in cna.get("metrics", []) for k in m)),
("CWE", any(x.get("cweId") for p in cna.get("problemTypes", []) for x in p.get("descriptions", []))),
("fix boundary", any(v.get("lessThan") or v.get("lessThanOrEqual") for v in versions)),
("patch/advisory tag", bool(tags & {"patch", "vendor-advisory", "release-notes"})),
] if not ok]
who = d["cveMetadata"].get("assignerShortName")
return f"{cve:17} CNA {who:12} missing: {', '.join(missing) or 'nothing'} | CISA SSVC exploitation: {ssvc or 'none'}"
if __name__ == "__main__":
ids = sys.argv[1:] or [l.strip() for l in sys.stdin if l.strip()]
for i in ids:
print(check(i))
On eight CVEs we've written about this month:
$ python3 cve_record_check.py CVE-2026-105192 CVE-2026-59180 CVE-2026-103030 CVE-2025-61624 \
CVE-2026-96627 CVE-2026-94603 CVE-2026-102764 CVE-2026-0322
CVE-2026-105192 CNA JFROG missing: patch/advisory tag | CISA SSVC exploitation: none
CVE-2026-59180 CNA GitHub_M missing: fix boundary, patch/advisory tag | CISA SSVC exploitation: poc
CVE-2026-103030 CNA GitHub_M missing: fix boundary, patch/advisory tag | CISA SSVC exploitation: poc
CVE-2025-61624 CNA fortinet missing: patch/advisory tag | CISA SSVC exploitation: active
CVE-2026-96627 CNA GitHub_M missing: fix boundary, patch/advisory tag | CISA SSVC exploitation: none
CVE-2026-94603 CNA GitHub_M missing: fix boundary, patch/advisory tag | CISA SSVC exploitation: none
CVE-2026-102764 CNA BT missing: patch/advisory tag | CISA SSVC exploitation: active
CVE-2026-0322 CNA palo_alto missing: nothing | CISA SSVC exploitation: active
Only one of the eight is complete by these four checks. The GitHub-assigned ones show the text-range pattern: the fix version is in the record, but not where a parser expects it. The three with active exploitation in CISA's SSVC data are the three that are in its Known Exploited Vulnerabilities catalog. Run it over the CVEs in your open findings and you'll see which ones your tooling is likely guessing about.
What to do
- Know which fields your process depends on. If your SLA keys on CVSS, 5% of 2026 records still have no score from either the CNA or CISA. If your scanner needs a fixed version, check how it reads ranges.
- Use the source closest to the package. For open-source CVEs, take versions and fixes from the GitHub advisory database or OSV. For vendor products, use the vendor advisory. Treat the CVE record as the identifier and summary.
- Use CISA's enrichment. The ADP container adds CVSS, CWE and SSVC exploitation status to most records. Our post on CNA status covers why who assigns a CVE matters.
- If you're a CNA, fill the structured fields.
lessThanfor the fixed version and apatchorvendor-advisorytag on the link cost little and make records usable by tools. - Watch for the criteria. When CISA defines record quality criteria, they'll likely shape what CNAs are expected to supply, and what you can expect to receive.
The fair view
The CVE Program's annual volume has roughly doubled since 2024 under a federated, largely volunteer model, and the data shows records getting steadily better through it. CNA-supplied scores and weaknesses are up about 10 and 12 percentage points since 2024. CISA's enrichment covers much of what CNAs leave out. Our four checks are also a blunt instrument: a record can pass them and still be wrong, or fail them and still be useful to a person.
But consumers don't read 81,803 records by hand. They depend on fields. A quality framework that starts with governance and participation is reasonable, because those are what make records better at the source. The test will be whether the criteria, when they arrive, measure what tools need. Until then, the gap is yours to close, and the script above shows where it is.