+− THE DAILY DIFFdev & AI news
SHIP IT

A Dependabot bump published a worm. 22 packages.

A bot opens a pull request.

A bot opens a pull request. A human merges it 24 minutes later. 95 minutes after that, a worm has published 110 malicious versions of his 22 npm packages, under his name, with a token it found in his CI.

Read the written edition (English) ↗

What this video covers

  • A bot, a human, a worm
  • How it happened, why npm allowed it, who gets the blame
  • Timeline: the poisoned cache
  • 21:48 — the Dependabot bump
  • Why it works

Transcript

A bot, a human, a worm

0:00 A bot opens a pull request; a human merges it twenty-four minutes later. Ninety-five minutes on, a worm has published a hundred and ten versions of his packages, as him. TanStack's postmortem has the upstream timestamps, the downstream maintainer's has the rest, and Hacker News gives it eleven hundred points and a name: Mini Shai-Hulud. How it happens, why npm allows it, who gets the blame.

How it happened, why npm allowed it, who gets the blame

0:21 This is The Daily Diff, postmortem. May 11th, morning. A renamed fork opens a pull request against TanStack Router. It closes within the hour, but a benchmark workflow has already run its code and

Timeline: the poisoned cache

0:33 saved a poisoned cache under the key the release workflow will use. Nineteen-twenty. A legitimate merge runs the release. The cache comes back, a binary reads the runner's memory, lifts the publish token, and ships eighty-four versions across forty-two packages, with valid provenance. Tests fail. It publishes anyway. Nineteen forty-six, a StepSecurity researcher files the issue; by nine UTC everything is deprecated and the advisory is out.

0:55 Deprecated is not gone: npm refuses to unpublish anything with dependents, so it stays installable for hours. Twenty-one forty-eight. In a nine-star aviation-data project, Dependabot opens its routine pull request: bump the dev-dependencies group, thirteen updates. Two are poisoned TanStack versions. Twenty-two-twelve: merged. The publish workflow runs npm C-I with the token in

21:48 — the Dependabot bump

1:15 scope; a prepare script reads it, and at twenty-two-seventeen the worm publishes as him. Five versions of every package the token reaches, even an old side project: one classic token for everything. A hundred and ten versions in ninety-five minutes. He finds out by email, after midnight. Why it works. One: npm install runs strangers' lifecycle scripts by default, and a git dependency's prepare script counts.

1:39 Two: the worm wants one thing: a token that publishes without a second factor. Then it asks the registry what else that maintainer owns, and republishes all of it with itself inside. Three: nothing in the chain is a person.

Why it works

1:51 A bot proposes, a pipeline installs, and the worm returns the favour: its dead-drop branches are named dependabot slash github-actions slash fremen. git blame. npm's install model, fifty-five percent: scripts run on install, deprecated stays installable, two-factor-bypass tokens exist. TanStack's CI, twenty-five: an unaudited pull request target workflow running fork code with write access to the cache. The bump habit, fifteen: thirteen updates merged in twenty-four minutes, token in the room.

2:17 Dependabot, five: so trusted the worm wears its uniform.

git blame

2:20 Blast radius: forty-two TanStack packages. Twenty-two downstream, from a project with nine stars. Over a hundred and sixty ecosystem-wide once the worm reaches Mistral. Every upstream version carried a valid signature: built by the official pipeline. True. Verdict, postmortem: SHIP IT. Both maintainers post timestamped postmortems within a day; within three, the downstream pipeline installs with ignore-scripts, splits build from publish, and drops the long-lived token.

Blast radius

2:47 npm's defaults haven't moved. Monday: ignore-scripts on install, and the publish token out of the job that runs it. Send me the incident you're still not allowed to talk about, in the comments, or at thedailydiff.dev. And that's the diff for today.

Verdict

3:00 I'm Niko from Axrisi. Merge responsibly.

Sources

  1. TanStack postmortem (Tanner Linsley, May 11, refined May 15)tanstack.com
  2. TanStack "Hardening TanStack After the npm Compromise" (May 12)tanstack.com
  3. TanStack/router#7383 — the StepSecurity detection issuegithub.com
  4. Downstream incident post, @squawk/* (May 12)github.com
  5. Downstream hardening post (May 14)github.com
  6. The Dependabot PR #246 (opened 21:48 UTC, merged 22:12)github.com
  7. @tan_stack advisory post, 21:19 UTCx.com
  8. StepSecurity — worm internals, bypass_2fa search, Dependabot-style branch nameswww.stepsecurity.io
  9. Socketsocket.dev
  10. Aikido — "over 160 packages, including Mistral"www.aikido.dev
  11. Hacker News (1,097 points)news.ycombinator.com

Related videos