- What: Supply chain compromise in TanStack npm packages
- Impact: Malicious versions of npm packages were published and quickly removed
by Tanner Linsley on May 11, 2026. Last updated:2026-05-11 On 2026-05-11 between 19:20 and 19:26 UTC, an attacker published 84 malicious versions across 42@tanstack/*npm packages by combining: thepull_request_target"Pwn Request" pattern, GitHub Actions cache poisoning across the fork↔base trust boundary, and runtime memory extraction of an OIDC token from the GitHub Actions runner process. No npm tokens were stolen and the npm publish workflow itself was not compromised. The malicious versions were detected publicly within 20 minutes by an external researcherashishkurmiworking forstepsecurity. All affected versions have been deprecated; npm security has been engaged to pull tarballs from the registry. We have no evidence of npm credentials being stolen, but we strongly recommend that anyone who installed an affected version on 2026-05-11 rotate AWS, GCP, Kubernetes, Vault, GitHub, npm, and SSH credentials reachable from the install host. Tracking issue:TanStack/router#7383GitHub Security Advisory:GHSA-g7cv-rxg3-hmpx 42 packages, 84 versions (two per package, published roughly 6 minutes apart). See the tracking issue for the full table. Confirmed-clean families:@tanstack/query*,@tanstack/table*,@tanstack/form*,@tanstack/virtual*,@tanstack/store,@tanstack/start(the meta-package, not@tanstack/start-*). When a developer or CI environment runsnpm install,pnpm install, oryarn installagainst any affected version, npm resolves the maliciousoptionalDependenciesentry, fetches the orphan payload commit from the fork network, runs itspreparelifecycle script, and executes a ~2.3 MB obfuscatedrouter_init.jssmuggled into the affected tarball. The script: Because the payload runs as part of npm install's lifecycle, anyone who installed an affected version on 2026-05-11 must treat the install host as potentially compromised. All times UTC. Local timestamps from GitHub API and npm registry. Three vulnerabilities chained together. Each is necessary for the attack; none alone is sufficient. bundle-size.ymlranpull_request_targetfor fork PRs and, inside that trigger context, checked out the fork's PR-merge ref and ran a build: The author of the workflow attempted a trust split (thecomment-prjob is separate frombenchmark-pr, with a comment in the YAML noting the intent to keepbenchmark-pr"untrusted with read-only permissions"). The split is correct in spirit but missed two facts: The maliciousvite_setup.mjswas specifically designed to write data into the pnpm-store directory under a key the legitrelease.ymlworkflow would compute and look up:Linux-pnpm-store-${hashFiles('**/pnpm-lock.yaml')}. When thebenchmark-prjob ended,actions/cache@v5's post-step saved the (now-poisoned) pnpm store to that exact key. Whenrelease.ymlnext ran on a push to main, its Setup Tools step restored the poisoned entry — entirely as designed. This is the class of attack documented by Adnan Khan in 2024. It's not a TanStack-specific bug; it's a known GitHub Actions design issue that requires conscious mitigation. release.ymldeclaresid-token: write(legitimately needed for npm OIDC trusted publishing). When the poisoned pnpm store is restored on the runner, attacker-controlled binaries are now on disk and get invoked during the build step. Those binaries: This is the same memory-extraction technique (and verbatim Python script, with attribution comment) used in thetj-actions/changed-filescompromise of March 2025. The attacker did not invent novel tradecraft; they recombined published research. The chain only works because each vulnerability bridges the trust boundary the others assumed: PR fork code crossing into base-repo cache, base-repo cache crossing into release-workflow runtime, and release-workflow runtime crossing into npm registry write access. Detection was external.carliniopened issue #7383 ~20 minutes after the publish, with full technical analysis. Tanner received a phone call from Socket.dev just moments after starting the war room confirming the situation. In any@tanstack/*package's manifest: These need answers before we close the postmortem.