Skip to main content

On 2 October a researcher published a cold account of a vulnerability in Xray-core, a widely deployed proxy. A certificate verification bypass shipped to users on 13 January 2026. The researcher reported it privately on 6 February. The maintainers fixed it the same day, in a commit whose message said it was there to “simplify the code”, and cut a release that mentioned no security problem at all. No advisory followed. On 3 July the researcher found the fix incomplete and filed a GitHub Security Advisory themselves. It went public on 2 October, 238 days after the first silent patch.

The Hacker News thread treated this as a scandal about one project’s ethics. We were interested in a different question: how unusual is it for the patch to be public long before the warning? So we measured it. Across npm, PyPI and Go, for every advisory published so far in 2026, the fix had already been sitting on the public registry for a median of 9.7 days. Xray-core is not an outlier in kind, only in degree.

TL;DR

  • We timed 6,119 GitHub Security Advisories published in 2026 across npm, PyPI and Go against the moment the patched version actually appeared on the public registry. Median gap: 9.7 days.
  • 1,826 of them (29.8%) arrived 30 or more days after the fix was installable. 810 arrived 60 days or more late; 325 arrived after 90 days.
  • Severity makes it worse, not better. 31% of CRITICAL npm advisories took 30 days or more, against 19% of LOW ones. The same inversion holds on PyPI.
  • 318 of those late advisories cover packages pulled more than a million times a week, including postcss (88 days), vitest, rated critical (81 days), undici (49 days) and nanoid (310 days).
  • Practical consequence: a team that simply upgrades patch releases weekly was already protected before the alert fired for 57% of 2026 advisories. Advisory-driven patching is a backstop, not a control.

What we measured, and how

OSV.dev publishes bulk exports of every advisory it holds, per ecosystem. We pulled the npm, PyPI and Go archives on 5 October 2026 and kept every GitHub Security Advisory with a 2026 publication date that names at least one fixed version. Withdrawn advisories and ones GitHub has itself flagged as duplicates were dropped.

For each one we took the first patched version it names and asked the registry when that version was actually published. The gap between that timestamp and the advisory’s publication date is the number we report: the window in which the fix was installable by anyone who cared to upgrade, and invisible to anyone waiting to be told.

Finding 1: the fix almost always lands first

Ecosystem Advisories Median gap 7 days or more 30 days or more 90 days or more
npm 2,712 8.3 days 53.7% 25.8% 4.6%
PyPI 1,863 10.9 days 58.2% 29.4% 5.9%
Go 1,544 13.3 days 62.2% 37.6% 5.9%
Pooled 6,119 9.7 days 57.2% 29.8% 5.3%

87.1% of advisories arrived at least a full day after the patched version was already on the registry. Only 2% arrived before any fix existed, which is the case everyone pictures when they think about disclosure. The common shape is the opposite: the patch is quietly available, and the notice catches up later. Go is the worst of the three and npm the best, but the gap between them is far smaller than the gap between any of them and zero. This is not one ecosystem’s culture. It is how the pipeline works.

Finding 2: the severity inversion

The intuition is that critical bugs get published fast and low-severity ones drift. The data says the reverse. On npm, 31.3% of advisories rated CRITICAL took 30 days or more to publish after the fix shipped, against 18.8% of LOW ones. PyPI shows the same inversion: 32.3% of CRITICAL against 16.8% of LOW.

We will not guess at motive from this data, but the plausible mechanical explanations all point one way: serious bugs attract embargoes, coordinated multi-maintainer fixes, CVE assignment and legal review, and each adds calendar time after the code has shipped. The effect is the same regardless of cause. The advisories you most need early are the ones most likely to be late.

Finding 3: this is not confined to obscure packages

Among advisories with a 30-day-plus gap, 318 cover 101 npm packages pulled more than a million times a week. Thirteen of those exceed 100 million weekly downloads.

Package Weekly downloads Severity Fix on registry Advisory published Gap
postcss 8.5.12 359.5m HIGH 26 Apr 2026 23 Jul 2026 88 days
vitest 4.1.0 142.1m CRITICAL 12 Mar 2026 1 Jun 2026 81 days
fflate 0.8.3 111.9m MODERATE 16 May 2026 22 Jul 2026 67 days
pnpm 11.11.0 245.2m HIGH 9 Jul 2026 2 Sep 2026 55 days
undici 8.2.0 247.7m HIGH 1 May 2026 19 Jun 2026 49 days
browserslist 4.28.7 241.4m HIGH 21 Jul 2026 1 Sep 2026 42 days
js-yaml 5.4.1 369.0m MODERATE 26 Aug 2026 29 Sep 2026 34 days

The nanoid case is the sharpest. GHSA-2v37-7h3g-55p8 was published on 29 July 2026; the patched version on the 5.x line, nanoid 5.1.6, went up on 22 September 2025. That is 310 days on a package pulled 321 million times a week.

Axios is the volume case rather than the duration case: twenty-two separate 2026 advisories landed 33 days or more behind their fix, arriving in two bulk batches, twelve on one day in July and ten on one day in September. If you were waiting to be told to upgrade axios, you were told twice, late.

What we are not claiming

Three caveats, all checked rather than assumed.

Registry publish time is not commit time. It is the first moment the fix was installable by the public, so the true silent window is longer than our figures, not shorter. We have reported the conservative number.

Backfill contaminates the long tail. GitHub periodically adds historical vulnerabilities to its database, producing enormous gaps that say nothing about disclosure practice: our worst npm case is a crypto-js advisory published in August 2026 against a fix from February 2020. So we re-ran everything restricted to advisories whose fix also shipped in 2026, which excludes backfill by construction. The npm median moves from 8.3 days to 7.7, the 30-day share from 25.8% to 23.7%. The finding does not live in the tail.

Multi-branch advisories need a choice. Where an advisory patches several release lines we used the earliest patched version. Re-running with the latest instead, the most conservative possible reading, gives an npm median of 7.9 days and 24.5% at 30 days or more. The variants agree.

Two coverage notes: 211 npm “fixed” versions named in advisories were not on the registry at all and were excluded, and on Go we resolved 1,544 of 1,819 eligible advisories (84.9%), the rest using pseudo-versions or renamed paths the proxy will not serve.

The control that actually works

Here is the number that should change what your team does on Monday. If the fix is public before the advisory, then a team on a routine upgrade cadence is protected by the upgrade, not by the alert. We calculated, for each ecosystem, what share of 2026 advisories had their fix available for longer than a given upgrade interval.

Upgrade cadence npm PyPI Go
Weekly 53.7% 58.2% 62.2%
Fortnightly 39.4% 46.3% 49.0%
Monthly 25.8% 29.4% 37.6%

Read that as: a team merging dependency bumps weekly was already on a patched version, for no security reason at all, by the time more than half of 2026’s advisories were published. A team that only moves when an alert fires caught none of them early, by definition, because the alert is the late thing.

That inverts how most budgets are set. Routine dependency maintenance is treated as hygiene, done when there is slack in the sprint, while the security spend goes to scanning. The measurement says the hygiene is the control and the scanner is the backstop.

Two more things before you set policy. 8% of 2026 npm advisories carry no CVE identifier, so tooling keyed to NVD enrichment never sees them; we covered what NVD’s prioritisation shift did to that pipeline in April. And of the 229,919 records in OSV’s npm feed, 222,302 are malicious-package takedowns rather than advisories: 96.7% of that channel is somebody else’s typosquat.

What to do with this

Four things, in the order we would do them.

Put a clock on dependency updates and make it weekly. Automated version-update pull requests, a CI suite you trust enough to merge on green, a named owner. The point is not chasing latest; it is that patch releases carry fixes you have not been told about yet.

Separate the two queues. Security alerts and routine upgrades are different work at different urgency, and one shared backlog guarantees the routine one starves.

Treat a quiet scanner as uninformative, not as good news. The median advisory describes something your lockfile could have been patched against nine days earlier. We have written before about scanners reading the wrong version number entirely, the same failure from another direction.

Read release notes for the ten dependencies that would actually hurt. Not all of them, which is unrealistic. The Xray-core fix was a public commit on 6 February with a misleading message; anyone watching that repository’s diffs had the information eight months before the advisory existed.

Hold the other half of the timeline in view: we measured how fast exploitation now follows disclosure in May. Attackers keep getting faster on the right of the clock; the left, the part nobody measures, still runs to a median of nine days.

REPTILEHAUS builds and runs the delivery pipelines that make a weekly upgrade cadence boring rather than frightening: tests worth merging on, staged rollouts, and the DevOps plumbing that turns dependency maintenance into something automated instead of deferred. If your updates are currently whatever happens when an alert gets loud enough, get in touch.

Method. OSV.dev bulk exports for npm, PyPI and Go, downloaded 5 October 2026: GitHub Security Advisories with a 2026 published date and at least one fixed event, excluding withdrawn and duplicate records. Fix availability from the npm registry time map, the PyPI JSON API upload_time_iso_8601 field and proxy.golang.org .info timestamps. Download figures from the npm downloads API, week ending 4 October 2026. Every figure here is reproducible from those four public endpoints.

📷 Photo by Laura Ockel on Unsplash