The TanStack Attack Did Not Steal a Maintainer Token. It Stole the Process Minting One.
The Shai-Hulud-derived wave that hit TanStack on May 11 was structurally different from every prior npm supply-chain compromise this year. The Axios maintainer hijack and the Trivy wave both rested on the same lever: steal a credential, publish from somewhere that looks like the developer. The TanStack attackers did not need a credential. They needed a few minutes of process memory on a GitHub-hosted runner, and they got it by submitting a pull request from a fork.
May 10, 17:16 UTC: The Fork
A throwaway account, zblgg (GitHub id 127806521), forked an unrelated repository named configuration. Six hours later, that fork received a single commit, 65bf499d, authored as claude <claude@users.noreply.github.com> to blend with the noise of agent-authored commits in modern repos. The commit added a postinstall hook that would execute on any machine running pnpm install against the fork.
May 11, 10:49 UTC: The Pull Request
The attacker opened PR #7378 against TanStack/router. At 11:11 UTC the PR was force-pushed to point at the malicious commit; at 11:29 UTC the TanStack bundle-size.yml workflow finished its run. Per the TanStack postmortem, that workflow "ran pull_request_target for fork PRs and, inside that trigger context, checked out the fork's PR-merge ref and ran a build." The pull_request_target trigger does not isolate fork code from the base repository's secrets and cache: it grants the fork code the privileges of the base repo's workflow context. This is the same class of PR-trigger trust failure I worked through in the CodeBreach AWS analysis, now applied to caches instead of webhooks.
When pnpm install ran, the malicious postinstall hook executed. It did not steal anything yet. It wrote a 1.1 GB poisoned pnpm store to the GitHub Actions cache, keyed Linux-pnpm-store-6f9233a50def742c09fde54f56553d6b449a535adf87d4083690539f49ae4da11. The postmortem is explicit on why this matters: "Cache scope is per-repo, shared across pull_request_target runs (which use the base repo's cache scope) and pushes to main. A PR running in the base repo's cache scope can poison entries that production workflows on main will later restore."
Two minutes after the cache was poisoned, at 11:31 UTC, the attacker force-pushed PR #7378 back to a clean main HEAD and closed it. The pull request that planted the bomb left no malicious code in any branch of any TanStack repo. The bomb sat in the cache.
May 11, 19:15 UTC: The Detonation
Eight hours later, the maintainer who handles router releases merged the unrelated PR #7369 into main. The merge triggered release.yml, which restored the cached pnpm store, ran pnpm install, and re-executed the planted postinstall hook in a workflow context with id-token: write and an npm publish token in environment. According to the StepSecurity analysis, the payload included a roughly 2.3 MB binary named tanstack_runner.js plus a root-level router_init.js of approximately the same size, placed outside the normal build paths.
The payload's first job was OIDC extraction. The postmortem describes the technique precisely: the binary located the GitHub Actions Runner.Worker process via /proc/*/cmdline, then read /proc/<pid>/maps and /proc/<pid>/mem to "extract the OIDC token (which the runner mints lazily, in memory, when id-token: write is set)." The attacker did not have an npm token at any point in this attack. They had the OIDC identity of the TanStack release runner, which npm accepts as proof of publish authority for OIDC-trusted packages.
At 19:20:39 UTC the first publish batch went out: roughly 42 packages and 84 versions, followed by a second batch at 19:26:14 UTC. The npm token used to authorize the publishes had its description set to IfYouRevokeThisTokenItWillWipeTheComputerOfTheOwner, a dead-man's switch that would wipe the disk of any system that revoked it. Researcher Ashish Kurmi filed issue #7383 approximately 20 minutes after the first publish.
What the SLSA Provenance Attested
Every malicious package generated a valid SLSA Build Level 3 provenance attestation. The attestations were technically accurate: each package was, in fact, built on a GitHub-hosted runner in the TanStack organization's release workflow, with the OIDC identity of that workflow. The attestation does not lie. It simply has no opinion on whether the workflow itself had been hijacked by a poisoned cache eight hours earlier.
This is the design boundary buyers need to internalize. SLSA Build Level 3 attests that the artifact was built on the claimed builder using the claimed source; it does not attest that the build environment was uncompromised, nor that the build inputs (caches, dependencies, postinstall hooks) were what the maintainer believed them to be. A build environment compromised through pull_request_target is still the claimed builder.
The Group Pattern
The attribution evidence ties this to TeamPCP, the same operators behind the Trivy compromise and the Trellix source-code repository access disclosed earlier this month. The Hacker News writeup names the spread beyond TanStack as reaching @squawk, @uipath, @tallyui, @mistralai, @opensearch-project, and Guardrails AI; SafeDep counted 404 malicious versions across more than 170 packages spanning npm and PyPI. StepSecurity attributes the wave to TeamPCP on the strength of campaign-specific tradecraft (Dune-themed branch names, a campaign-specific PBKDF2 salt, and obfuscation patterns matching the Trivy compromise).
What differs in this wave is the attack surface. Prior waves harvested credentials from compromised developer machines, including in the Axios downstream RAT case. The TanStack wave harvested an in-memory OIDC token from a GitHub-hosted runner. The attacker never touched a maintainer's laptop, never phished a token, and never needed npm credentials. They needed pull_request_target against a base-repo cache scope, and a downstream release.yml that would restore the cache and mint an OIDC token in the same process.
Why This Is a Procurement Question, Not a Vendor Vulnerability
The Register framed this as a variant of a 2024 GitHub Actions cache-poisoning vulnerability, and it is. Adnan Khan published the definitive writeup in May 2024. GitHub Security Lab acknowledged the pattern, and the November and December 2024 mitigations narrowed the hole. They did not close it, because pull_request_target is designed to share cache scope across the fork-to-base trust boundary. The trigger exists specifically because some workflows need access to base-repo secrets when responding to fork PRs; isolating cache by trigger would break that. OpenSSF documented the patterns at length the same year.
What this means for procurement is that every npm package your application pulls in has a release pipeline you have never reviewed, and the SLSA provenance attached to that package will not tell you whether the pipeline was structured safely. The vendor questionnaires used to evaluate SaaS dependencies do not currently ask about it. Neither does the SOC 2 attestation, a gap the Copy Fail Linux kernel disclosure made concrete yesterday: SOC 2 attests that controls existed, not that they covered the failure mode that hit you.
The same procurement gap appears in the Self-Hosted AI Shadow IT analysis and in the Five Eyes agentic-AI procurement work: vendor diligence is built around artifact attestation, not architecture review. The TanStack wave is the first compromise I have seen where the artifact attestation is provably truthful and substantively useless at the same time.
The Question to Add to Your Vendor Questionnaire This Week
For every npm-publishing vendor whose packages reach production, ask one question and require evidence: does any workflow in your release repositories use pull_request_target against a job that runs untrusted fork code, restores a shared cache scope, or operates in the same repository as a workflow that mints an OIDC token for npm publish? If the answer is yes and the workflows are not isolated by separate repos or by cache-key partitioning, the vendor's SLSA Build Level 3 provenance is attesting to the builder identity of a process that any GitHub user can hijack with a fork and a postinstall hook.