← All posts

The TanStack npm compromise of May 11, 2026

TeamPCP chained pull_request_target, GitHub Actions cache poisoning, and OIDC token extraction to publish 84 malicious @tanstack versions in six minutes.

Koban Team
  • tanstack
  • npm
  • github-actions
  • supply-chain

On May 11, 2026, between 19:20 and 19:26 UTC, an attacker published 84 malicious versions across 42 @tanstack/* npm packages without stealing any maintainer npm tokens. TanStack's official postmortem documents how three GitHub Actions weaknesses chained together to turn a legitimate release workflow into a publish path for malware.

What happened

The attack combined:

  1. pull_request_target with fork code checkout in bundle-size.yml, a pattern GitHub Security Lab calls the "Pwn Request"
  2. GitHub Actions cache poisoning across the fork-to-base trust boundary (documented by Adnan Khan in 2024)
  3. OIDC token extraction from runner memory, reusing tradecraft from the tj-actions/changed-files compromise of March 2025

On May 10, an attacker forked TanStack/router as zblgg/configuration. On May 11, PR #7378 triggered pull_request_target workflows that checked out fork-controlled code and poisoned the pnpm store cache keyed to what release.yml would restore on the next push to main.

When release workflows ran later that evening, the poisoned cache restored attacker-controlled binaries. Those binaries read the GitHub Actions Runner.Worker process memory, extracted an OIDC token minted for npm trusted publishing, and POSTed directly to registry.npmjs.org. The publish did not come from the workflow's defined Publish Packages step, which was skipped because tests failed.

Detection and scope

External researcher ashishkurmi (StepSecurity) opened TanStack/router#7383 at 19:46 UTC, roughly 26 minutes after the first malicious publish. TanStack deprecated all 84 versions; npm removed tarballs between 22:13 and 23:55 UTC the same night.

TanStack confirmed that only Router/Start monorepo packages were affected. Families including @tanstack/query*, @tanstack/table*, and @tanstack/form* were clean. See GHSA-g7cv-rxg3-hmpx for affected version numbers.

What the payload does on install

If a developer or CI runner installed an affected version on May 11, the malicious optionalDependencies entry triggered a prepare script running router_init.js. TanStack's postmortem states the script:

  • Harvests credentials from AWS, GCP, Kubernetes, Vault, npm, GitHub, and SSH key locations
  • Exfiltrates over Session/Oxen messenger file-upload endpoints
  • Self-propagates by searching for other packages the victim maintains and republishing them with the same injection

Anyone who installed on that date should treat the host as potentially compromised and rotate reachable credentials.

Why developer Macs matter

CI runners were the primary blast radius, but any engineer who ran pnpm install or npm install against a poisoned version on a laptop pulled the same lifecycle payload. Lockfiles updated on that machine are the artifact Koban diffs on the next heartbeat.

Frozen-lockfile installs (npm ci, pnpm install --frozen-lockfile) against a pre-incident lockfile would not have resolved the malicious versions. Teams without pinned lockfiles in local workflows were exposed.

Lessons that outlive TanStack

TanStack's postmortem highlights uncomfortable truths:

  • OIDC trusted publishing minted valid tokens for attacker-controlled code. Provenance proves which workflow built an artifact, not that the workflow ran the code you intended.
  • The GitHub Actions cache is a shared trust boundary that most teams do not treat as one.
  • pull_request_target plus checkout of PR head code remains dangerous years after public warnings.

Visibility on developer Macs does not stop cache poisoning. It does show when a new @tanstack/* version or unexpected dependency appears in a lockfile after an incident window.

Further reading