arielshemesh1999@gmail.com · Israel
← All articles

The npm data heist

On 11 May 2026 a self-propagating worm tore through npm — TanStack and 400+ packages compromised in minutes. Here is what happened — and what I changed on my own machine the same week.

What happened

On 11 May 2026 a threat-actor group launched a coordinated supply-chain attack on the npm and PyPI ecosystems. Researchers named the campaign “Mini Shai-Hulud” — a smaller echo of an earlier self-replicating npm worm. TanStack was hit first: in a six-minute window (19:20–19:26 UTC) the attacker published 84 malicious versions across 42 @tanstack/* packages. These are not obscure packages — TanStack Query alone pulls in the order of 200 million downloads a month. Within hours, Socket had flagged 400+ compromised packages, with Mistral AI, UiPath and OpenSearch also caught in the blast.

How they got in

No password was phished. The attacker abused the victims’ CI. They chained a GitHub Actions pull_request_target “Pwn Request”, Actions cache poisoning, and a runtime extraction of an OIDC token straight out of the Actions runner’s memory. That token was enough to publish new releases as the real maintainer — no human in the loop.

Why it spread on its own

This was a worm, not a one-off. Install any affected version and the payload runs from an npm lifecycle hook. It steals GitHub tokens, npm tokens, and AWS / GCP / Azure credentials, then looks at which packages you have publish access to, injects the same malicious dependency into them, and publishes fresh poisoned releases with your stolen credentials. Each victim becomes the next launch point.

The destructive twist

The payload polls api.github.com/user with the stolen token every 60 seconds. The moment that token is revoked — that is, the moment someone starts cleaning up — it runs rm -rf ~/ and wipes the home directory. Incident response itself becomes the trigger.

Why npm install is the weak point

npm packages can declare postinstall scripts that execute automatically during install, with your user’s full permissions. A modern project pulls hundreds of transitive dependencies; you vetted none of them by hand. One compromised package deep in the tree runs code on every machine that installs the project — laptops, CI runners, build servers alike.

What I changed

I treat my dev machine as if a malicious package will run, not might. Concretely: npm config set ignore-scripts true so lifecycle scripts do not fire on install; dependency installs happen inside a sandboxed dev environment, not against my real home directory; the macOS firewall runs in stealth mode; and I keep outbound-connection and ransomware monitors running so any exfiltration attempt is visible. Screen locks immediately, secrets live in a manager — not in plaintext .env files lying around.

The takeaway

You cannot read every dependency. So shrink the blast radius instead: commit lockfiles, pin versions, run npm audit, disable install scripts by default, harden CI so a pull request cannot reach your publish tokens, and never run untrusted installs as a user that can see your real keys. Convenience is the attack surface.