On May 11, 2026, an attacker published 84 malicious npm package versions across 42 @tanstack/* packages in two waves six minutes apart (19:20–19:26 UTC). No maintainer was phished and no token was stolen from a laptop. Instead, the attacker chained three GitHub Actions vulnerabilities to steal a publish token from the CI runner's memory.

The Attack Chain

The attacker opened a pull request from a throwaway fork of TanStack/router. The PR was immediately closed, but the bundle-size.yml workflow had already triggered. That workflow used pull_request_target, which runs in the base repo's privileged context without the first-time-contributor approval gate. The workflow checked out the fork's PR-merge ref and ran pnpm nx run @benchmarks/bundle-size:build – attacker-controlled code.

That code poisoned the pnpm cache. Later, a legitimate merge to main triggered the release workflow, which restored the poisoned cache. The malicious code then extracted the short-lived publish OIDC token from the runner's memory and used it to publish the malicious versions.

The Malware's Payload

When anyone runs npm install, pnpm install, or yarn install on an affected version, npm resolves the optionalDependencies array. The attacker added a pointer to a specific commit in a GitHub fork containing a malicious router_init.js. That script does three things:

  1. Credential theft: Walks through ~/.npmrc, GitHub tokens (env vars, gh CLI config, .git-credentials), SSH private keys, and cloud runner credentials.
  2. Exfiltration via Session messenger: Uses Session's file-upload servers as a dead drop. Traffic looks normal and is end-to-end encrypted, so network monitoring can't see the contents. Blocking Session domains is the only network-level defense.
  3. Spreading: The worm part – one compromised maintainer leads to compromised packages, then more maintainers.

Affected Packages and Clean Families

Confirmed clean: @tanstack/query*, @tanstack/table*, @tanstack/form*, @tanstack/virtual*, @tanstack/store, and the @tanstack/start meta-package (but not @tanstack/start-*). Anyone who installed an affected version on 2026-05-11 must treat the install host as potentially compromised.

Immediate Actions

  1. Check lockfiles for @tanstack/* resolutions dated 2026-05-11. The IOC fingerprint is an optionalDependencies entry pointing to "@tanstack/setup": "github:tanstack/router#79ac49eedf774dd4b0cfa308722bc463cfe5885c" and a router_init.js file at the package root.
  2. If hit, rotate everything reachable from that install host: AWS, GCP, Kubernetes, Vault, GitHub, npm, and SSH credentials.
  3. Pin to known-good versions. TanStack deprecated all 84 affected versions; npm security pulled the tarballs.
  4. Audit your own pull_request_target workflows. If any checks out a fork's code and runs it, you have the same vulnerability.

Lessons Learned

pull_request_target has been documented as a bad security practice for over three years. TanStack had it in production, and so does a meaningful fraction of every popular OSS repo on GitHub. Run zizmor against your workflows this week.

The attack didn't require phishing or stolen tokens – it used the trust model of CI caching and the elevated permissions of pull_request_target. The worm component means this could have spread further if not caught quickly. The attacker engineered a path where their own CI pipeline stole its own publish token at the exact moment it was created, via a cache everyone implicitly trusted.

Technical Breakdown of the Vulnerabilities

  • pull_request_target: Runs in the base repo's context, with access to secrets. It checks out the merge commit of the fork, but the code is attacker-controlled. No approval gate for first-time contributors if the workflow lacks pull_request event restrictions.
  • Cache poisoning: The attacker poisoned the pnpm cache during the compromised PR run. The cache key was based on the branch or hash, but the cache was restored across different workflows because the cache namespace is shared.
  • OIDC token extraction: The attacker read the ACTIONS_ID_TOKEN_REQUEST_TOKEN environment variable from the runner's memory. This token could be exchanged for a cloud provider's access token if OIDC is configured. In this case, it was used to publish to npm.

How to Protect Your CI

  • Replace pull_request_target with pull_request and use github.event.pull_request.head.repo.full_name != github.repository to skip untrusted forks.
  • If you must use pull_request_target, never check out and execute code from the fork. Use it only for actions like labeling or commenting.
  • Use cache scoping per workflow and branch to prevent cross-workflow poisoning.
  • Rotate secrets frequently and use short-lived tokens with minimal permissions.
  • Monitor your CI logs for unexpected cache restores or OIDC token usage.

The TanStack incident is a textbook example of supply-chain compromise via CI pipeline misconfiguration. It's not about phishing – it's about trust boundaries in automation.