Measured 24 August 2026 · 235,992 advisories · 7 ecosystems · 1,488 packages probed live

The quarter million that isn't

Every headline about registry security quotes the number of malicious-package advisories and stops there. That number tells you nothing about how many describe a real attack, or whether anything was taken down. So we measured it: the full OSSF advisory set, a stratified sample probed against the live registries, and every non-campaign hit downloaded and opened. The result points the opposite way to the collapse narrative.

Source: OSSF Malicious Packages via OSV.dev, MAL- records only Registry state: each registry's own public API, 24 August 2026 Sample: 1,488 advisories, seed 42, reproducible
Headline numbers
Advisories in the database
235,992
Across seven ecosystems. 351 withdrawn as false alarms, 0.149%, excluded throughout.
npm advisories that are one campaign
64.0%
140,728 records describe the same tea token-farming spam. Zero of them in any other ecosystem.
Real advisories still installable
0.5%
2 of 399, confirmed by opening the archive. The naive figure is 25.9%.
Removed within 24 hours
24.1%
Of npm removals with a dated receipt. Median 405.5 hours, and the tail is long.

Advisories by ecosystem

One toggle changes the whole picture. The tea campaign is an automated publisher of derivative packages under random names, chasing token rewards; it exists only on npm. Counting it makes npm look sixteen times more dangerous than PyPI. Dated receipt marks the registry that records when a package went away.

Ecosystem Advisories Share Of which campaign Relative

 

Detection is not evenly spread either. Of the 219,998 npm records, 146,010 come from a single scanner, amazon-inspector, then 22,453 from ghsa-malware, 5,895 from reversing-labs, 4,876 from ossf-package-analysis, 819 from google-open-source-security and 71 from checkmarx. The distribution reflects who is scanning, not only what is happening. Full dataset: data/supplychain.json.

From 25.9% to 0.5%: the same question, measured four times

The question is "can you still install the flagged thing today". Each of the first three answers is publishable as a scandal, and each is wrong. The row that matters is the last one, and it is the only one where somebody opened the archive.

Measurement Exposed Basis What it gets wrong Relative

What each measurement missed

A live package is not a live vulnerability

25.9% to 23.5%Asking "does this package name still resolve" counts every compromised real library whose poisoned versions were surgically excised. The package lives on, the flagged version 404s. Nineteen of 800 sampled advisories are exactly that case, and by advisory type it works: where the advisory names specific versions rather than the whole package, exposure is 3.3% against 27.2%.

Version numbers get reused

22,603 bytes of nothingjest-canvas-mock 2.5.3 was published by a compromised npm account in a 22-minute burst of 631 malicious versions across 314 packages, each with a preinstall hook running a 498 KB obfuscated payload. Its tarball returns 200 today. Inside there is no hook and no bundle: the real maintainer reused the version number after the takedown. An HTTP status code cannot tell those two apart.

A spam campaign is not an attack

46.0% against 1.0%Within our sample, exposure among tea-campaign advisories is 46.0% and among everything else 1.0%. Blending them produces a number driven almost entirely by nobody bothering to clean up token spam. It is a real registry-hygiene problem and it is not credential theft, so the page counts it separately rather than folding it in.

A withdrawn advisory is a false accusation

1 of 4One of the four non-campaign hits, @budetzz/libsignal-node 2.0.15, sits under an advisory the reporter withdrew on 26 May 2026. Publishing it as still-exposed would have named a package that was never malicious. The withdrawn field is 0.149% of the database and it has to be honoured.

Three ways npm removes a package, and what each means for the name

This is the mechanism that makes the whole measurement possible on npm and impossible elsewhere.

State What the registry serves Dated? What happens to the name
Tombstone A single version 0.0.1-security with repository: npm/security-holder Yes, and the original version dates survive alongside it Locked permanently. Nobody can claim it again.
Unpublish A time.unpublished record with the exact stamp and the list of versions pulled Yes, to the second Back in circulation. Somebody else can register it, which is a risk in itself.
Version excision The package, minus the poisoned versions, whose tarballs 404 Partly: the surviving versions keep their dates Unchanged. This is the path for a compromised genuine library.
Hard delete, everywhere else 404, nothing else No PyPI 98.3%, RubyGems 99.0%, NuGet 98.7% of sampled advisories. Yes or no, never when.

Time to removal, and the bulk sweep that fakes it

Measured from the package being published to it being removed, for npm removals with a dated receipt, N=381.

Median
405.5 h
About 17 days. Quartiles 29.8 h and 1,427 h.
Within 24 hours
24.1%
34.4% within seven days.
90th percentile
7,620 h
Ten and a half months. The tail carries the risk.
Removed before the advisory
44.2%
47.6% after, 8.2% in the same hour.

Two things about this table are easy to get wrong, so both are stated rather than smoothed over.

The first is a sweep masquerading as a response time. Run the same calculation over the tea campaign alone and the median comes out at 13,147 hours, about a year and a half, with a suspiciously tight spread: p25 12,862 h, p75 13,265 h. That is not latency, it is one date. 204 of 216 removals in that group happened in November 2025, in a single bulk cleanup. A distribution that narrow is always worth distrusting; here it turned out to be an artefact, and the campaign is excluded from the figures above.

The second is which clock to start. We measure from the package's own publication date, not from the advisory, because 44.2% of dated removals happened before the advisory existed. Counting from the advisory would produce negative durations for nearly half the sample, and a much prettier median for the rest. For the subset genuinely removed after an advisory, the median lag is 35.9 hours, p75 57.2 h, p90 460.0 h. That is the honest answer to "how fast does a report get acted on", and it is a different question from "how long was this package installable".

The two that are still live

Every non-campaign hit was downloaded and opened. Four candidates, two survived.

Package and version Advisory Verdict after opening the archive
streak-view-core 1.0.0 MAL-2026-12471 Confirmed exposed. The archive contains indexCache, matching the advisory description. Flagged 5 August 2026, so 19 days of exposure at the measurement date.
boxy-temper 35.1.5 MAL-2026-13087 Confirmed exposed. Contains _bridge.js and a reference to workers.dev, again matching the advisory. Also flagged 5 August 2026.
jest-canvas-mock 2.5.3 MAL-2026-4136 Not exposed. Returns 200 and 22,603 bytes, but the archive holds only ordinary library files. The version number was reused after removal.
@budetzz/libsignal-node 2.0.15 MAL-2026-4373 Excluded. The advisory was withdrawn on 26 May 2026.

Methodology and known gaps

The measurement is simple enough to restate in full, which is the point: anybody can reproduce it against the same two public sources.

  1. Advisory set: the OSSF Malicious Packages database as distributed by OSV.dev, per-ecosystem dumps, downloaded 24 August 2026, counting only records whose id carries the MAL- prefix. Records with a withdrawn field, 351 of 235,992, are excluded everywhere.
  2. Campaign identification: an advisory counts as tea-campaign if its details text names the tea protocol. That is a text match on the advisory, not an independent judgement about the package, and it is deliberately conservative.
  3. Registry probe: a stratified sample of 800 npm advisories, 400 campaign and 400 not, drawn with a fixed seed of 42 so the selection is reproducible, plus 300 PyPI, 200 RubyGems, 150 NuGet and every crates.io (19), Go (18) and Packagist (1) record. Each was resolved against that registry's own public API.
  4. Content verification: where the flagged version still returned 200, the tarball was downloaded and searched for the indicators the advisory itself describes. Only this step can distinguish a live poisoned version from a reused version number.
  5. Sample, not population. 800 of 219,998 npm advisories. At 400 per group the error on a rate near 1% is comparable to the rate itself, which is why the headline is reported as "2 of 399" and not only as a percentage.
  6. crates.io, Go and Packagist have N of 19, 18 and 1. No percentage from those is a meaningful indicator. A low advisory count can mean a genuinely cleaner ecosystem or a coverage gap in the OSSF database, and we do not claim to know which.
  7. Eight of 18 Go queries to proxy.golang.org returned HTTP 403 and are counted in no category rather than assumed either way.
  8. The 46.0% campaign exposure figure is weaker than the rest. Those 184 hits were not opened, so that number is exposure at the level of an HTTP status code, and it is reported separately from the content-verified 0.5% for exactly that reason.
  9. The advisory database is not complete and aggregates six sources with different methodologies, one of which contributes 146,010 of 219,998 npm records. Publication timestamps in npm's time field are as declared by the registry; we have no independent source to confirm them.

One conclusion is worth stating plainly, because it runs against the way this category is usually written up: on the evidence here the registries are not failing to remove attacks. Real malicious packages are taken down close to completely, in nearly half of cases before the advisory that describes them is even published, and what inflates the public counters is an unremoved crypto-reward spam campaign on a single registry. That is a hygiene problem, and it is a different problem.

Sources

Questions

How many malicious packages are there really?

The OSSF Malicious Packages database held 235,992 advisories with the MAL- prefix when we pulled it on 24 August 2026, and 219,998 of those are npm. The count is accurate, but 140,728 npm advisories, 64.0% of them, describe one automated campaign that published derivative packages under random names to farm tea protocol token rewards. That payload spams the registry rather than stealing anything. Take the campaign out and npm drops from 93.2% of all advisories to roughly 83%.

How many flagged packages can you still install today?

Of 399 real npm advisories in our sample, 2 still serve an installable poisoned version, which is 0.5%. Both were flagged on 5 August 2026, so 19 days of exposure as of the measurement date. Naive versions of the same measurement return 25.9%, 23.5% and 46.0%, and all three are wrong for reasons we set out in the methodology section: they miss surgical version removal, version-number reuse, and the difference between a spam campaign and an attack.

Why can time to removal only be measured on npm?

Because npm is the only registry that leaves a dated receipt. It has three removal states, and each one keeps timestamps: a security-holder tombstone at version 0.0.1-security that preserves the original version dates, an unpublish record with an exact time.unpublished stamp, and surgical removal of individual versions from a package that stays alive. PyPI, RubyGems and NuGet delete hard: a query for a flagged package returns 404 with no dates at all. In our sample that was 98.3% of PyPI, 99.0% of RubyGems and 98.7% of NuGet. Those registries can answer only yes or no, never when.

How fast do registries remove a malicious package?

For npm removals with a dated receipt, excluding one bulk sweep, the median time from the package being published to it being removed is 405.5 hours, roughly 17 days, with a quartile range of 29.8 to 1,427 hours. 24.1% are gone within 24 hours and 34.4% within seven days. The tail is long: the 90th percentile sits at 7,620 hours. Counting from the advisory instead of from publication would give a much shorter and much more flattering number, and also a misleading one, because 44.2% of removals happened before the advisory was even published.

Does an advisory cause the takedown?

Often not. For removed packages with a dated receipt, 44.2% were removed before the advisory was published, 47.6% after, and 8.2% within the same hour. So in close to half of cases the advisory is a record of a takedown that already happened, not the trigger for one. For the ones removed after the advisory, the median lag is 35.9 hours.

Is an HTTP 200 on a tarball proof that a package is still dangerous?

No, and this is the single trap that makes most published figures too high. We opened every non-campaign hit. jest-canvas-mock 2.5.3 returns 200 and 22,603 bytes, but the archive holds no preinstall hook and no large bundle, only ordinary library files: the compromised version was removed and the real maintainer later reused that version number. One other hit turned out to be a withdrawn advisory, which would have been a false accusation. Only two of four survived, and the only conclusive test is downloading the archive and looking for the indicators the advisory describes.