Upstream APIs
Every number on patch-clock is computed at runtime from one of the following public, unauthenticated APIs. We never invent data.
| Source | URL | Frequency | What we read |
|---|---|---|---|
| GitHub Security Advisories (global) | https://api.github.com/advisories?ecosystem={eco}&affects={pkg} |
every 6h, per tracked package | ghsa_id, cve_id, published_at, withdrawn_at, severity, cvss, summary, description, references[], vulnerabilities[].package, .patched_versions, .vulnerable_version_range |
| OSV.dev cross-check | POST https://api.osv.dev/v1/query |
every 4h, per tracked package | id, aliases (GHSA-*), published, affected[].ranges[].events[].fixed — used to catch advisories GHSA misses |
| GitHub Releases | https://api.github.com/repos/{owner}/{repo}/releases |
every 12h, per tracked repo | tag_name, name, published_at — used to find the publish date of the first patched release |
| GitHub Tags + Commits (fallback) | /repos/{owner}/{repo}/tags then /repos/{owner}/{repo}/commits/{sha} |
on-demand, when Releases is empty for a patched range | tag name + commit committer.date |
| npm registry | https://registry.npmjs.org/{pkg} |
on-demand | time["1.2.3"] — publish ISO timestamp per version |
| PyPI JSON | https://pypi.org/pypi/{pkg}/json |
on-demand | releases[version][0].upload_time_iso_8601 |
How patch lag is computed
For every advisory whose patched_versions string is parseable (e.g. >= 1.4.3 or >= 0.31.0, < 0.32.0 || >= 0.32.1):
- We pick the lowest concrete version that satisfies any group of the constraint.
- We look up that version's publish timestamp using, in order: GitHub Releases cache → GitHub Tags + commit committer date → npm registry's
timemap → PyPI'supload_time_iso_8601. patch_lag_hours = first_patched_release_at − advisory_published_at.- A negative lag means the patched release predated the advisory (stealth-fix). We keep the negative value and flag it as
pre_disclosure_fix; it does not count toward median/mean MTTP.
Failure handling
Every fetcher logs to a fetch_log table with source, status, error, and duration. If GHSA returns 403/422, we back off exponentially and continue with the next package. If a release-lookup fails, we leave first_patched_release_at = NULL and the advisory appears on the unconfirmed wall. We never invent a date to fill a gap.
Limitations
- We only cover GHSA-indexed CVEs (plus a small set OSV catches that GHSA misses).
- We depend on accurate
patched_versionsstrings. Maintainers occasionally publish wrong ranges — we surface what GHSA tells us. - If a vendor ships a fix in a major release weeks after a hotfix patch was available, we may underestimate the urgency.
- We do not measure response to private CVEs or vendor-disclosed advisories outside GHSA / OSV.
Tracked frameworks
The current seed list lives in lib/tracked.js. To add a framework, append an entry with at least one (ecosystem, package_name) pair and a github_repo for release-date lookups.