Open Source

Inside the TanStack npm Supply-Chain Compromise

A pwn request, a poisoned Actions cache, and an OIDC token dumped from runner memory let attackers publish 84 malicious versions of 42 TanStack npm packages on May 11, 2026.

Inside the TanStack npm Supply-Chain Compromise — article cover
On this page6 SECTIONS
  1. Scope: 42 Packages, 84 Versions, One Monorepo
  2. The Attack Chain: Three Bugs, All Necessary
  3. What the Malware Did
  4. Detection: An Outsider Won the First 26 Minutes
  5. Five Things Maintainers Should Change
  6. Sources

Between 19:20 and 19:26 UTC on May 11, 2026, 84 malicious versions of 42 TanStack packages landed on npm. Nothing looked wrong to the registry, because nothing was forged: the packages came out of TanStack’s own Router/Start release workflow, authenticated with a valid OIDC identity through GitHub Actions trusted publishing. No npm tokens were stolen, and the legitimate publish pipeline itself was never breached. TanStack’s postmortem, published after a three-day security sweep, reconstructs how three ordinary misconfigurations chained into full control of a release pipeline for a heavily downloaded open-source project.

That is why this incident matters beyond one CVE. OIDC trusted publishing is supposed to be the safe alternative to long-lived npm tokens. But if an attacker can make your runner mint a publish token, then “who can trigger a workflow” becomes “who can ship malware to every downstream install.”

Scope: 42 Packages, 84 Versions, One Monorepo

The damage was confined to the Router/Start monorepo. The affected set includes @tanstack/history at 1.161.9 and 1.161.12, plus 41 siblings such as react-router, vue-router, solid-start, router-core, and router-generator — two malicious versions per package, published roughly six minutes apart. @tanstack/query, table, form, virtual, and store, along with every other TanStack repository, were confirmed clean. The malicious code rode in through a fake optional dependency, @tanstack/setup, pinned to orphan commit 79ac49ee: any npm, pnpm, or yarn install pulled down and executed a roughly 2.3 MB obfuscated script named router_init.js.

The Attack Chain: Three Bugs, All Necessary

Step one was a “pwn request.” The bundle-size.yml workflow used the pull_request_target trigger and checked out the fork’s PR merge ref (refs/pull/7378/merge) before running a build — executing attacker-controlled code inside an environment with repository permissions, while also bypassing the approval gate for first-time contributors. The groundwork started a day earlier: on May 10, an account named zblgg created a fork renamed to “configuration” to evade fork searches, then authored a roughly 30,000-line payload under the forged identity “claude”, email claude@users.noreply.github.com — no relation to Anthropic’s Claude — and marked it [skip ci].

Step two was Actions cache poisoning. The payload wrote files into the pnpm store at the exact cache key the release.yml workflow would later compute. Because PR runs and pushes to main share one cache scope, and the post-job cache save was not gated by permissions, a 1.1 GB poisoned cache entry landed at 11:29 UTC on May 11. The attacker then reverted the PR to a zero-file no-op, closed it, and deleted the branch. The PR looked clean; the poison stayed.

Step three was OIDC token theft. At 19:15 that evening, a re-run of the release workflow restored the poisoned cache. The attacker’s binaries scanned /proc command lines for the Actions Runner.Worker process, dumped its memory, extracted the lazily-minted OIDC token (the workflow carried id-token: write for legitimate npm trusted publishing), and POSTed directly to registry.npmjs.org — skipping the Publish Packages step entirely. The technique was lifted almost verbatim from the March 2025 tj-actions/changed-files compromise.

What the Malware Did

Router_init.js did three things. It harvested credentials: AWS IMDS and Secrets Manager, GCP metadata, Kubernetes service-account tokens, Vault tokens, ~/.npmrc contents, GitHub tokens, and SSH private keys. It exfiltrated them through Session messenger endpoints such as filev2.getsession.org, end-to-end encrypted with no conventional C2 server to sinkhole — network defenders are left blocking IPs and domains. And it self-propagated: the script queried npm for other packages the victim maintained and republished them with the same injection.

Detection: An Outsider Won the First 26 Minutes

The fastest alarm came from outside the project. At 19:46:46 UTC, StepSecurity researcher ashishkurmi opened issue #7383 on the TanStack repository, about 20 to 26 minutes after the first malicious publish. Tanner Linsley and Manuel then raced through containment: the two @tanstack/history versions were deprecated at 20:19, the full scope by 21:03 — one hour 43 minutes after publish. By 21:30 the root cause was identified, all caches purged, a hardening pull request merged, and advisory GHSA-g7cv-rxg3-hmpx published (CVE-2026-45321, CVSS 9.6 Critical). npm removed the tarballs registry-side between 22:13 and 23:55, roughly four and a half hours after publish. The story drew 1,097 points and 465 comments on Hacker News. TanStack’s guidance to users is blunt: treat any host that installed an affected version on May 11 as compromised, and rotate AWS, GCP, Kubernetes, Vault, GitHub, npm, and SSH credentials.

Five Things Maintainers Should Change

The postmortem’s self-criticism is more useful than the attack anatomy. First, the project had no internal monitoring or alerting on publishes — detection came entirely from outside. Second, audit every pull_request_target workflow and add repository_owner condition guards. Third, pin third-party actions to commit SHAs instead of floating refs like @main or @v6. Fourth, gate cache saves behind explicit permissions. Fifth, and most fundamental, rethink OIDC trusted publishing: unreviewed automatic publishing is too convenient, so move to short-lived classic tokens with manual review, or add provenance source verification. The seven-maintainer npm scope should also shrink its blast radius while they are at it.

Sources

AI-assisted summary compiled from the sources above, reviewed by a human before publishing.

FOUND_THIS_USEFUL?

Support more practical AI articles, tutorials, and build notes.

BUY_ME_A_COFFEE
SHAREXEMAIL