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:
- Credential theft: Walks through
~/.npmrc, GitHub tokens (env vars, gh CLI config, .git-credentials), SSH private keys, and cloud runner credentials. - 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.
- 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
- Check lockfiles for @tanstack/* resolutions dated 2026-05-11. The IOC fingerprint is an
optionalDependenciesentry pointing to"@tanstack/setup": "github:tanstack/router#79ac49eedf774dd4b0cfa308722bc463cfe5885c"and arouter_init.jsfile at the package root. - If hit, rotate everything reachable from that install host: AWS, GCP, Kubernetes, Vault, GitHub, npm, and SSH credentials.
- Pin to known-good versions. TanStack deprecated all 84 affected versions; npm security pulled the tarballs.
- Audit your own
pull_request_targetworkflows. 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_requestevent 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_TOKENenvironment 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_targetwithpull_requestand usegithub.event.pull_request.head.repo.full_name != github.repositoryto 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.




