Teardown: Mini Shai-Hulud and the TanStack npm compromise

84 malicious versions in six minutes, published by TanStack's own release pipeline.

What happened on 11 May 2026, how the attacker got publish rights without stealing a token, and where Chainsaw would and would not have stopped it.

Questions and answers

What happened?

The TanStack wave of Mini Shai-Hulud is the npm worm attack of 11 May 2026, in which an attacker published 84 malicious versions of 42 @tanstack/* npm packages between about 19:20 and 19:26 UTC, two versions per package a few minutes apart. The versions went out through TanStack’s own trusted-publisher binding, and each one pulled in a git-hosted optional dependency whose prepare script stole credentials. The incident is tracked as CVE-2026-45321 and GitHub advisory GHSA-g7cv-rxg3-hmpx. A StepSecurity researcher reported it about 26 minutes after the first publish; TanStack deprecated the first versions about 59 minutes after publish, and npm removed the last one about 4 hours 35 minutes after publish.

How did the attacker publish as TanStack?

The Mini Shai-Hulud attacker published as TanStack by hijacking the project’s own release pipeline on 11 May 2026; no npm token was stolen. According to TanStack’s postmortem and the GitHub advisory, the attacker chained three weaknesses in the TanStack GitHub Actions setup:

  1. A pull_request_target workflow ran code from an attacker’s fork in the base repository’s trusted context (a “Pwn Request”).
  2. The fork code poisoned the GitHub Actions cache across the fork and base trust boundary.
  3. When the real release workflow restored the poisoned cache, attacker code read the OIDC token from the runner’s memory and published directly to npm through TanStack’s own trusted-publisher binding.

The publish workflow itself was never modified. To anything that checks who published a release, the 84 malicious versions looked like ordinary TanStack releases.

What ran when a developer installed it?

The Mini Shai-Hulud entry point is a prepare script in a git-hosted optional dependency, not the TanStack package’s own install scripts. Each malicious @tanstack/* manifest added an optional dependency, @tanstack/setup, that pointed at a git commit in the TanStack router repository rather than at the npm registry. During install, npm fetched the commit and ran the prepare script: bun run tanstack_runner.js && exit 1.

The trailing exit 1 made the optional dependency fail, so npm discarded @tanstack/setup and left nothing in node_modules. By then tanstack_runner.js had already started a roughly 2.3 MB obfuscated router_init.js. A check that looks only for preinstall or postinstall in the published package sees nothing here. Chainsaw’s rule that blocks git-URL dependencies would have refused the shape on day 0, but that rule is not switched on by default.

What did it steal, and where did the data go?

Mini Shai-Hulud is a credential-stealing worm. The payload, a roughly 2.3 MB obfuscated router_init.js, collected AWS instance-metadata and Secrets Manager credentials, GCP metadata, Kubernetes service-account tokens, Vault tokens, ~/.npmrc, GitHub tokens and SSH private keys from each machine that installed a malicious @tanstack/* version. The theft happened during npm install itself: tanstack_runner.js started the payload before npm discarded the failing optional dependency, so nothing was left in node_modules to inspect. The stolen data left via Session (Oxen), a file-upload network that is end-to-end encrypted and has no attacker-run server to take down. The payload then republished other packages the victim maintained, so each infected maintainer spread the attack onward to other maintainers’ packages.

Where does this sit in the Shai-Hulud family?

Mini Shai-Hulud is the May 2026 wave of the Shai-Hulud npm worm family. The original Shai-Hulud hit over 500 npm packages in September 2025, according to CISA, and Shai-Hulud 2.0 followed in November 2025. Each wave moved the malicious code further from where defenders were looking: from an install script in the package, to a preinstall hook running the Bun runtime, to the prepare script of a git-URL optional dependency published through hijacked trusted publishing.

WaveWhenWhere the code ranScale reported
Shai-HuludSeptember 2025Install script in the package; stolen secrets pushed to public GitHub repos named “Shai-Hulud”Over 500 npm packages (CISA)
Shai-Hulud 2.0 (“The Second Coming”)November 2025preinstall hook that installs the Bun runtime and runs the payload under itMore than 25,000 GitHub repos of stolen data (Wiz, via mnemonic)
Mini Shai-Hulud, TanStack waveMay 2026prepare script of a git-URL optional dependency, published through hijacked trusted publishing84 versions of 42 packages in six minutes, then onward to other maintainers’ packages

Would Chainsaw have stopped it?

Yes, but not by reading the bytes on day 0: Chainsaw would have stopped the TanStack wave of Mini Shai-Hulud for installs that went through the Chainsaw proxy. The evidence is our re-run of 58 attacks from 2026 against production Chainsaw (v0.22.118) on 19 October 2026:

  • The release hold kept every malicious version out. By default Chainsaw holds back any version published in the last 48 hours, and extends the hold to 96 hours when a release changes shape. These versions were 3.7 times larger than the previous release and added a new git dependency, so the hold was 96 hours. An install asking for the latest version resolved to the previous good release.
  • The malware feed made it permanent. All 84 versions were listed in the known-malicious feed Chainsaw carries 4.2 to 5.2 hours after publish, well inside the hold, so the versions stay refused after the hold ends.
  • No default rule refused it on the bytes. A rule that blocks git-URL dependencies would have refused the TanStack versions on first download, on day 0. Chainsaw has that rule, but it is not switched on by default, because some teams depend on git URLs legitimately.
  • Installs that bypassed the proxy got the malware. A laptop or CI runner pointed straight at registry.npmjs.org was not protected. Chainsaw therefore also gates CI, pins laptops through MDM and blocks direct registry traffic at the network.

What should you do?

If you installed any @tanstack/* version published on 11 May 2026, the most urgent step is the one in GitHub advisory GHSA-g7cv-rxg3-hmpx: treat the machine or runner as compromised, rotate every credential the install could reach, and reinstall from a clean lockfile. The Mini Shai-Hulud attacker published 84 malicious versions of 42 @tanstack/* packages that day, between about 19:20 and 19:26 UTC.

For the next wave, four habits each break part of this chain:

  1. Hold new releases. A 24 to 72 hour release-age hold, at a proxy or with your package manager’s minimum-release-age setting, would have outlasted this attack’s entire public life.
  2. Refuse git-URL dependencies in production installs unless you have listed them.
  3. Don’t run fork code in pull_request_target, and keep GitHub Actions caches from crossing from forks into release workflows.
  4. Treat trusted publishing as proof of who published, not proof that the code is safe.

Sources

One teardown per notable attack

Get the next teardown by email

What happened, the install-path angle most coverage misses, and the rule that refuses it.