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
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.
| Column | What it holds |
|---|---|
| id | The finding's natural key, stable across snapshots: patchdrift:<kind>:<record>:<vendor>:<product>:<before>:<after>. The same value is the feed guid. |
| kind | Which kind of edit this row is. The site calls these kinds of change; /api/v1/stats lists every kind this file carries. |
| source | Which catalog the row came from: cvelistV5 for the CVE catalog, kev for CISA's exploited list, ghsa for GitHub-reviewed advisories. |
| cve_id | The CVE record this row belongs to. On an advisory row, the first CVE the advisory names, or empty. |
| ghsa_id | The GitHub advisory this row belongs to. Empty on CVE-catalog and KEV rows. |
| cna | The organisation that published the record, usually the vendor itself. Empty on rows that came from CISA's list, which names no publisher. |
| vendor | Vendor as the record states it. Empty on the kinds of change that apply to a whole record rather than to a named product. |
| product | Product as the record states it, on the kinds of change that name one. |
| ecosystem | The package ecosystem an advisory row names: npm, PyPI, Maven and the rest. |
| package | The package an advisory row names, as it appears in a dependency file. |
| before | The earlier source statement: publication baseline for catalog findings, prior field state for KEV edits. |
| after | The later source statement. KEV edits describe the state after that historical edit. |
| published_at | The record's or advisory's own publication instant, ISO-8601. |
| observed_at | When 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_commit | The 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_days | How 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_basis | The 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. |
| confidence | verified 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. |
| counted | True exactly when this row is counted in a figure on this site. Always false on a suspect row. |
| fp_class | Which of the refusal classes threw this row out. On suspect rows only. |
| flags | Space-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. |
| reason | Why a refused row was judged as it was, in the detector's own words. |
| record_url | The record's page on this site. |
| upstream_url | The record on cve.org, or the advisory on github.com, so you can read it yourself. |
| details | Kind-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.
| Path | Returns | Example |
|---|---|---|
| GET /api/v1/moves | array 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/cna | array 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/vendors | array of vendor objects | /api/v1/vendors?limit=50 |
| GET /api/v1/signals | array 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/advisories | array 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/digest | array of day objects | /api/v1/digest?days=7&signal=added_to_kev |
| GET /api/v1/stats | single stats object | /api/v1/stats |
| POST /api/v1/check | object: { 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.json | OpenAPI document | /api/v1/openapi.json |
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.