TanStack published its npm supply-chain postmortem. The attack chained three GitHub Actions flaws.
Attackers compromised 42 TanStack packages through a pull_request_target exploit, cache poisoning, and OIDC token theft. An external researcher caught it in 20 minutes.
TanStack published a full postmortem on May 11 detailing how attackers compromised 42 of its npm packages. The attack hit 84 versions in under six minutes, and an external security researcher caught it in 20. The packages that were hit don’t include TanStack Query, Table, or Form, the libraries most developers know the project for.
What makes this postmortem worth reading isn’t the malware. It’s the attack chain. Three distinct GitHub Actions weaknesses, each well-documented individually, were combined into a single exploit path that published credential-stealing code through TanStack’s own legitimate CI pipeline. No npm tokens were stolen. No maintainer accounts were breached. The attacker never needed either.
How the attack worked
The chain started with TanStack’s bundle-size.yml workflow, which used the pull_request_target trigger. That trigger is a known footgun in GitHub Actions: it runs the workflow with the base repository’s write permissions, even when the pull request comes from a fork. GitHub documents the risk. Most projects ignore it.
An attacker created a fork at github.com/zblgg/configuration and submitted PR #7378 with a malicious vite_setup.mjs commit. Because the bundle-size workflow ran fork-controlled code without sandboxing, the attacker’s script executed in the context of TanStack’s repo.
Step two was cache poisoning. GitHub Actions caches the pnpm store to speed up installs. The cache key, Linux-pnpm-store-6f9233a50def742c09fde54f56553d6b449a535adf87d4083690539f49ae4da11, was shared across trust boundaries. The attacker’s PR run wrote a poisoned pnpm cache entry. When the next legitimate workflow fired, it restored that cache.
Step three was the actual payload delivery. The attacker extracted an OIDC token from the runner’s memory during the PR workflow. With that token, they could authenticate to npm’s provenance-based publish system and trigger a publish that looked like it came from TanStack’s own CI. The result: 84 malicious package versions published between 19:20 and 19:26 UTC on May 11.
What the payload did
The injected code was a 2.3MB obfuscated file called router_init.js. It harvested credentials from every major cloud and secrets provider: AWS IMDS metadata, GCP instance metadata, Kubernetes service account tokens, HashiCorp Vault tokens, .npmrc files, GitHub tokens, and SSH keys.
Exfiltration went through the Session/Oxen network via filev2.getsession.org and seed nodes. The script also attempted self-propagation by republishing other packages the maintainer had access to.
The payload also included persistence mechanisms: a Claude Code settings hook that re-executes the malware on every session, a VS Code task trigger, and platform-specific daemons on macOS (LaunchAgent) and Linux (systemd). A dead man’s switch monitored for token revocation and attempted rm -rf ~/ if credentials were pulled.
The advisory is tracked as GHSA-g7cv-rxg3-hmpx, scored at CVSS 9.6.
How it was caught
An external researcher working under the handle “carlini” at StepSecurity opened issue #7383 roughly 20 minutes after the malicious versions appeared. The report included a complete technical breakdown. Socket.dev separately called the maintainers by phone around the same time.
TanStack’s own admission in the postmortem is blunt: “We learned about the compromise from a third party. We need monitoring on our own publishes.” They had no alerting for unexpected npm publishes, which is how 84 versions went out without anyone on the team noticing.
The bigger picture: SLSA provenance doesn’t save you
This is the first documented case of a malicious npm package carrying valid SLSA Build Level 3 provenance. The Sigstore attestations on the compromised @tanstack/* versions are legitimate: they correctly attest that the packages were built and published by release.yml running on refs/heads/main in the TanStack/router repository. Every signature checks out.
The vulnerability: SLSA provenance confirms which pipeline produced the artifact. It does not confirm that the pipeline was behaving as intended. Snyk’s analysis spells it out: “SLSA provenance attests that a package was built by a specific repository’s GitHub Actions run. It does not attest that the workflow was authorized to run, that it executed from a protected branch, or that the commit triggering it was legitimate.”
Socket.dev put it more sharply: “An implant-published package that carries a ‘verified provenance’ badge will pass cursory security reviews.” That’s the sleeper finding. Provenance badges on npm packages are treated as trust signals by security tools and manual reviewers alike. After TanStack, they prove less than most people assumed.
Part of a larger wave
The attack is part of what security firms are calling “Mini Shai-Hulud Wave 5,” the latest escalation by threat group TeamPCP. The May 11 wave wasn’t just TanStack. It was a coordinated cross-ecosystem attack hitting 169 npm packages and 2 PyPI packages simultaneously. Targets included @uipath/* (65 packages), @mistralai/* (3 npm packages plus the mistralai PyPI package), and @opensearch-project/opensearch (1.3 million weekly downloads).
The campaign marker recovered from the payload: EveryBoiWeBuildIsAWormyBoi. The git-tanstack[.]com domain was registered two days before the attack.
Previous waves hit Bitwarden CLI, PyTorch Lightning, SAP packages, and Aqua Security’s Trivy. The package count keeps growing: from 4 SAP packages to 84 TanStack versions to 404 total malicious versions across the May 11 wave. Endor Labs noted: “The package count is expanding, not contracting.”
What TanStack changed
The remediation list reads like a GitHub Actions hardening guide:
- All 84 versions deprecated on npm; tarballs pulled server-side.
- Every GitHub Actions cache entry purged across TanStack repos.
- Push permissions reduced to a single maintainer (Tanner Linsley).
- The
bundle-size.ymlworkflow was restructured to eliminatepull_request_target, and all third-party action references were pinned to commit SHAs instead of floating tags. - Formal malware reports filed with npm.
The postmortem also flags two systemic issues TanStack can’t fix alone. First, npm’s unpublish policy prevented immediate removal because other packages depend on the compromised versions. Malicious tarballs remained downloadable for hours. Second, the npm scope had seven maintainers, any of whose stolen credentials would’ve been enough. TanStack is reducing that number.
The postmortem’s self-assessment is refreshingly honest. Under “what could have been better”: “No internal alerting. We learned about the compromise from a third party.” Under “got lucky on”: “Attacker’s payload broke tests, which made the publish step skip.” The attack was loud enough to detect because the malware was sloppy enough to fail unit tests.
GitHub responded with a discussion titled “Our plan for a more secure npm supply chain,” outlining staged publishing (a review period before packages go live), deprecation of classic tokens, and mandatory FIDO-based 2FA for publishes.
What this means for you
If you use TanStack Router or any @tanstack/* package outside of Query, Table, Form, Virtual, Store, and Start, check whether you pulled a version published on May 11 between 19:20 and 19:26 UTC. The advisory has the complete version list. TanStack recommends rotating AWS, GCP, Kubernetes, Vault, GitHub, npm, and SSH credentials on any machine that installed an affected version.
If you maintain an open-source project with GitHub Actions, audit your workflows for pull_request_target. If you use it, make sure fork-controlled code never runs with write permissions. Pin every action reference to a full commit SHA, not a tag. Tags can be moved; SHAs can’t.
The broader pattern is clear. The last three months have produced a supply-chain attack roughly every week: CanisterWorm, Mini Shai-Hulud, the Bitwarden CLI worm, and now TanStack. Each used a different entry point. None required breaking into a maintainer’s account directly. The common thread is that CI/CD pipelines are the new attack surface, and most projects treat them as trusted infrastructure when they aren’t.
Share this article
Quick reference
Sources
Frequently Asked
- Which TanStack packages were compromised?
- 42 @tanstack/* packages received 84 malicious versions (2 per package). TanStack Query, Table, Form, Virtual, Store, and Start were confirmed clean.
- What did the malicious code do?
- A 2.3MB obfuscated script harvested credentials from AWS, GCP, Kubernetes, Vault, npm, GitHub, and SSH, then exfiltrated them via the Session/Oxen network.
- Were any npm tokens stolen?
- No. The npm publish workflow itself was not compromised. The attacker exploited GitHub Actions trust boundaries to trigger a legitimate publish with poisoned code.
- How long were the malicious versions available?
- They were published in a 6-minute window on May 11. An external researcher flagged them within 20 minutes. All 84 versions were deprecated, but tarballs remained downloadable for hours until npm pulled them server-side.