Skip to content
Now taking design partners.Roadmap →

Campaign postmortem · July 13, 2026

Shai-Hulud: 700 packages, thirty minutes

The Shai-Hulud npm worm's second wave backdoored 796 packages inside a single evening. Here is how the mechanic works, why traditional tooling missed it, and what a fleet operator should do the next time it lands.

By Sekeye team· 6 min· npm · worm · credential-rotation · postmortem

The compressed timeline

November 2025. Shai-Hulud wave 2 opens with a legitimate maintainer session being replayed against npm publish. Within roughly thirty minutes of the initial tainted upload, 796 npm packages carried a backdoor. By morning, security teams were reading a takedown thread that named more packages than most of them had ever explicitly reviewed.

Wave 1, in September 2025, hit about 200 packages and 500 versions before the coordinated OpenSSF and npm response landed. Wave 2 was materially larger because the operator learned from wave 1. Better 2FA handling. Faster credential harvesting. A tighter republishing loop.

The point of this post is not the count. It is the mechanic.

Why a worm compresses time

Most supply-chain compromises are one-shot. A single package. A single maintainer. A single window of exposure.

Shai-Hulud is not that. Every backdoored install ships a postinstall script that runs on the host that installs it. The script harvests any npm token in ~/.npmrc, GitHub PATs from the gh CLI config, cloud CLI profiles, and SSH keys. Those tokens are then used to authenticate to npm and republish tainted versions of every package the compromised maintainer owns.

Every developer machine that runs npm install on a tainted package becomes a potential re-publisher. The graph fans out. You do not get a linear victim count. You get a compounding curve.

That is what "thirty minutes" bought.

What most tools miss

Three gaps show up every time this pattern lands:

  1. EDR is looking at binaries. node, npm, and the postinstall script are all normal execution from a normal process tree. Nothing trips a behavioural rule. The credential file reads look like standard developer workflow.
  2. Dependency scanners look at your repos. They catch the tainted version in your package-lock.json when your CI runs. They do not catch the developer laptop that installed the same package four hours ago and shipped credentials off the box before your scanner ran.
  3. SBOM inventory is stale. A weekly scan is a week late. A daily scan is a day late. This wave played out in half an hour.

The gap is not detection. The gap is where you are looking.

What the catalog does

The Sekeye catalog listed every affected version range within the same day. That is not the interesting part. Every serious feed did the same.

The interesting part is what happens next. Drig has already inventoried package.json, package-lock.json, yarn.lock, and pnpm-lock.yaml across every enrolled endpoint, streaming package plus version plus hash into the backend on a continuous cadence. When a new campaign template lands in the catalog, the backend re-matches the current fleet snapshot against it. No rescan on the endpoint. No fresh cron. No waiting for the next scheduled inventory pass.

The console shows the exposed hosts, the tainted version, the install source path, and the developer identity that was resident at exposure time. That is the queue your responder actually needs.

What to do the next time this lands

The rotation sequence is boring and it matters. In order:

  1. Revoke every npm token issued to affected machines (npm token revoke).
  2. Cycle GitHub personal access tokens and OAuth app tokens on the same hosts. Include any fine-grained tokens that a gh auth status output would have exposed.
  3. Rotate SSH keys and cloud CLI credentials that were resident on those hosts during the exposure window.
  4. Force IdP re-authentication for the developer identities involved. Session cookies were on the box too.
  5. Rebuild lockfiles from a clean base after upstream versions are restored. Do not paper over the compromised version by pinning to a later good one without inspecting the intermediate range.

The credential rotation is the point. The lockfile bump is the smaller half of the job.

Wave 3 will come

Worm ecosystems do not stop after two waves. The mechanic is too cheap and the payoff too large. When wave 3 lands, the response window will be whatever the fleet's current inventory freshness allows. That is a lever worth pulling before the wave, not after.

Sekeye ships Shai-Hulud 2.0 as a signed campaign template today. The next worm gets the same treatment inside the freshness window, without a Drig release, without a fleet-wide rescan, and without asking a responder to compose the query themselves at 2am.

That is the promise. Ask a design partner what it looks like in practice.