patch-clock

Data sources & how patch lag is computed.

Upstream APIs

Every number on patch-clock is computed at runtime from one of the following public, unauthenticated APIs. We never invent data.

SourceURLFrequencyWhat 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):

  1. We pick the lowest concrete version that satisfies any group of the constraint.
  2. We look up that version's publish timestamp using, in order: GitHub Releases cache → GitHub Tags + commit committer date → npm registry's time map → PyPI's upload_time_iso_8601.
  3. patch_lag_hours = first_patched_release_at − advisory_published_at.
  4. 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

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.