Skip to content

Take the data

Every edit we counted, as a file for a spreadsheet or your own tools, and as JSON for a script.

Two files

Every edit to a CVE record or a KEV entry

5,000 rows in the file of 11,999 counted.

Only the fix version moves

365 rows in the file.

One row is one edit to one product, not one CVE: a record that gained four affected products is one record and four rows. Each row names the upstream commit it came from. The 5,026 edits to GitHub advisories belong to an advisory rather than a CVE record, are counted in their own unit, are not in these files and are never added to the 11,999 rows above. /api/v1/advisories serves them whole.

Cut short: one download gives you at most 5,000 rows, and a file that was cut short says so on a # line at the top. Narrowing by kind or by year gets you a complete file for that slice. Every counted row is in the shipped file, so a complete slice is a complete answer for what this analysis could see. The download of refused rows is cut to at most 250 refused rows for each reason we refuse a change, newest first; 14,393 refused rows are counted by reason but not in that file.

Not checked: a CVE record published before 2023 is never compared, and a flaw already on CISA's exploited list on the day we started watching shows no addition, so a record missing from these rows is not evidence that it never moved. What each catalog cannot see.

The file holds only the changes we counted. The 18,323 we compared and refused are counted nowhere and download separately (the refused rows, as CSV, ?confidence=suspect); the advisory engine refused a further 2,291 version pairs, counted by reason and never written out. All 35 reasons are on the method page. Code is MIT; the figures derived here are CC0; the underlying records belong to their publishers.

Columns

The Finding contract: the same fields /api/v1/signals returns, in snake_case, published as JSON Schema at /schema/finding.v1.json. CVSS is the 0 to 10 severity score a record carries, and the bands it is read in.

ColumnWhat it holds
idThe finding's natural key, stable across snapshots: patchdrift:<kind>:<record>:<vendor>:<product>:<before>:<after>. The same value is the feed guid.
kindWhich kind of edit this row is. The site calls these kinds of change; /api/v1/stats lists every kind this file carries.
sourceWhich catalog the row came from: cvelistV5 for the CVE catalog, kev for CISA's exploited list, ghsa for GitHub-reviewed advisories.
cve_idThe CVE record this row belongs to. On an advisory row, the first CVE the advisory names, or empty.
ghsa_idThe GitHub advisory this row belongs to. Empty on CVE-catalog and KEV rows.
cnaThe organisation that published the record, usually the vendor itself. Empty on rows that came from CISA's list, which names no publisher.
vendorVendor as the record states it. Empty on the kinds of change that apply to a whole record rather than to a named product.
productProduct as the record states it, on the kinds of change that name one.
ecosystemThe package ecosystem an advisory row names: npm, PyPI, Maven and the rest.
packageThe package an advisory row names, as it appears in a dependency file.
beforeThe earlier source statement: publication baseline for catalog findings, prior field state for KEV edits.
afterThe later source statement. KEV edits describe the state after that historical edit.
published_atThe record's or advisory's own publication instant, ISO-8601.
observed_atWhen the change first shows up in the catalog's history. Not when the vendor changed their mind, which nobody publishes. Empty when no commit was located.
observed_commitThe upstream commit carrying the reported later state. A publication comparison can span intermediate edits; its before value need not be in this commit's parent.
lag_daysHow long the earlier value stood, ending at its first observed replacement. The reported current value may have appeared later. Empty means unknown, never zero.
lag_basisThe prior stated-value interval's starting clock: publication for endpoint comparisons; observed introduction of the prior field value for KEV edits. Unknown starts or gaps are null, never zero.
confidenceverified for a change we counted, suspect for one we compared and then refused. Only verified is ever counted, and suspect rows come back only with ?confidence=suspect or all.
countedTrue exactly when this row is counted in a figure on this site. Always false on a suspect row.
fp_classWhich of the refusal classes threw this row out. On suspect rows only.
flagsSpace-separated qualifiers: same_day, bulk, unlocated, band_crossed, in_publication_batch. A same-day or bulk row is literally true and still counted; the flag is there so you can decide.
reasonWhy a refused row was judged as it was, in the detector's own words.
record_urlThe record's page on this site.
upstream_urlThe record on cve.org, or the advisory on github.com, so you can read it yourself.
detailsKind-specific facts as a JSON object: the CVSS bands on a rescore, CISA's dateAdded and due date on a KEV row, the severity on an advisory row. Empty when there are none.

The CSV follows RFC 4180 with CRLF line endings, behind a block of # comment lines; set # as the comment character in your importer rather than stripping them, because they say when the file was cut short or filtered. A request with neither signal= nor confidence= is served the older thirteen-column fix-version file, frozen for scripts that read it by position; every link on this page sets signal=.

The API

Read only, no key, open to any origin. Every response carries its own caveats in meta.notes. The machine-readable index is /api/v1, the schema /api/v1/openapi.json.

PathReturnsExample
GET /api/v1/movesarray of move objects (frozen v1 shape; see /api/v1/signals for the Finding contract)/api/v1/moves?year=2025&cna=apple&limit=25
GET /api/v1/cve/{id}single record object; `amendments[]` are Findings (see /schema/finding.v1.json), each with `details.disposition` of counted, filtered or context, and boundary rows carrying `details.branch`, `details.operator`, `details.operatorAtPublication`; `durability` is the per-record object a scanner ingests: { amendments, countedAmendments, kinds[], firstMovedDaysAfterPublication, lastMovedDaysAfterPublication, longestStoodDays, currentBoundaries[{vendor, product, branch, statedAtPublication, statedNow, operator}], flags{sameDay, bulkCommit, inKev, forensicTriage}, verdictVocabulary }/api/v1/cve/CVE-2026-65675
GET /api/v1/cnaarray of publisher rows: { cna, year, records, recordsStatingFix, recordsStatingLastAffected, moved, extended, rate, rateExtended, wilsonLow, wilsonHigh, shrunkRate, medianStoodDays, events, shape{fixMoved, rangeExtended, bulk}, tooFew, ranked, rank, overlapsNext, fingerprint[6], fingerprintRows, quarantinedRows, links }; `meta.ranking` carries { measurableFloor, movedFloor, shrinkPriorRecords, pooledRate, ranked, tooFew, belowMeasurableFloor, distinguishableThrough }/api/v1/cna?year=all
GET /api/v1/cna/{name}single publisher object: { cna, all: Publisher | null, years: Publisher[], findings: Finding[], quarantinedRows, links }; `meta.total` counts ROWS and `meta.links.next` pages/api/v1/cna/microsoft?year=2026&limit=50
GET /api/v1/vendorsarray of vendor objects/api/v1/vendors?limit=50
GET /api/v1/signalsarray of Findings, the one row shape every surface emits (/schema/finding.v1.json)/api/v1/signals?signal=product_added&year=2026&limit=25
GET /api/v1/advisoriesarray of Findings whose subject is an advisory (ghsaId, ecosystem, package); GitHub's severity rides in `details.severity`/api/v1/advisories?ecosystem=pypi&package=django
GET /api/v1/digestarray of day objects/api/v1/digest?days=7&signal=added_to_kev
GET /api/v1/statssingle stats object/api/v1/stats
POST /api/v1/checkobject: { checkSchemaVersion, checked, findings: Finding[], verdicts: [{ cveId, patchedTo, verdict, position, branch, operator, findingId }], products: [{ query, vendor, product, version, vendors, ambiguous, matched, records, findings: Finding[], quarantined: Finding[], verdicts }], coverage, advisories: Finding[] }curl -X POST /api/v1/check -H 'content-type: application/json' -d '{"ids":["CVE-2026-65675@1.132.1"],"products":["linux@6.1.181"]}'
GET /api/v1/openapi.jsonOpenAPI document/api/v1/openapi.json

RSS →JSON Feed →This week →

Data sources and quality

We watch 15 kinds of change in all; the files above carry the 11 that apply to a CVE record or a KEV entry, computed 2026-09-07. A catalog commit is one saved revision of the public repository we read, so the hash below names the exact state these rows were counted from.

Shipped snapshot computed 2026-09-07 from catalog commit 1f78fd2580c1. Real findings, not live ones: records amended since are not reflected. A later fix version is evidence that the record changed, not evidence that the first fix was incomplete.

Every figure here comes from one analysis computed 2026-09-07 against catalog commit 1f78fd2580c1, shipped as one file and read by every page, feed and API response alike. The checks that refuse each kind's false positives run inside that analysis, so a count here and the same count in the download or the API are the same number.

The three repositories, and what each one cannot see →