The Incident
On 2026-05-11 between 19:20 and 19:26 UTC, 84 malicious versions were published across 42 @tanstack/* npm packages, including @tanstack/react-router 1.169.5 and 1.169.8. The advisory is tracked as CVE-2026-45321 / GHSA-g7cv-rxg3-hmpx at CVSS 9.6 (Critical). The malicious versions were emitted by TanStack’s own release workflow under its trusted OIDC identity to npm Trusted Publishing, and carry valid SLSA Build Level 3 provenance attestations chained to the legitimate TanStack/router GitHub repository — the first documented case of a malicious npm package shipping with a valid Sigstore-issued provenance.
Per the TanStack postmortem, Endor Labs, and Orca Security, the attack chain ran end-to-end inside the build pipeline. A fork from attacker account zblgg (with a sibling account voicproducoes reportedly aged seven weeks earlier per StepSecurity) opened a pull_request_target-triggered workflow run that wrote a poisoned pnpm-store cache entry keyed for the release workflow. When a later maintainer merge to main ran the release workflow, the poisoned cache was restored; an attacker binary located the GitHub Actions Runner.Worker process via /proc/*/cmdline, dumped /proc/<pid>/mem, extracted the lazily-minted OIDC id-token, and posted directly to registry.npmjs.org — bypassing the workflow’s own Publish Packages step. External researcher ashishkurmi (StepSecurity) detected the malicious uploads within roughly 20–26 minutes. The payload harvests AWS IMDS and Secrets Manager values, GCP metadata, Kubernetes service-account tokens, Vault tokens, ~/.npmrc, GitHub tokens, and SSH keys; it installs a gh-token-monitor persistent daemon (LaunchAgent on macOS, systemd unit on Linux) that polls GitHub every 60 seconds and, on a 40X response, attempts rm -rf ~/. The campaign is attributed to TeamPCP — the same threat cluster Microsoft and SecurityWeek tied to the March 2026 Aqua Trivy compromise and the April 2026 Bitwarden CLI npm hijack. Per Mend, the wave expanded to roughly 172 packages and 403 versions across npm and PyPI within 48 hours, including Mistral AI, Guardrails AI, UiPath, Squawk, and the OpenSearch JavaScript client. MITRE ATT&CK coverage: T1195.002 Compromise Software Supply Chain, T1528 Steal Application Access Token, T1552.005 Cloud Instance Metadata API, T1543.001 Launch Agent, T1485 Data Destruction.
The Authority Path That Failed
The identity that carried execution authority at the moment of failure was TanStack’s GitHub Actions release workflow — specifically, the OIDC id-token minted in-memory on a Runner.Worker process when id-token: write was set, accepted by npm’s Trusted Publisher configuration as the publication principal. That identity held the right to publish @tanstack/* packages from the legitimate release.yml workflow on main. What it exercised, after the cache-poisoning chain, was publishing 84 attacker-authored tarballs from code that was never reviewed or approved by TanStack maintainers. The critical gap was not a stolen npm token at rest; it was untrusted bytes restored into a trusted release job that already had authority to mint a short-lived publish identity.
The trust anchor that failed first was the pull_request_target trigger: it ran fork-controlled code in the base repository’s cache scope, then let that job write an actions/cache entry later restored by the release workflow. Workflow-path and branch-ref binding are still table stakes for Trusted Publisher policies, but they are not sufficient against this chain: TanStack’s malicious publishes came from legitimate main release runs after the poisoned cache crossed the fork-to-release boundary. The held-vs-exercised gap was inside the release job itself. Once the poisoned pnpm store was restored, attacker-controlled code could run before or around the intended publish step, reach the runner process, extract the lazily minted OIDC token, and publish directly to npm.
SecurityV0 Perspective
This maps to nhi_compromise (ASI06). The end-to-end chain — pull_request_target cache-write privilege, poisoned pnpm store, OIDC token extracted from Runner.Worker memory, npm Trusted Publisher publish call — consumed only machine identities. No npm publish token at rest was stolen; the TanStack postmortem confirms no npm tokens were compromised and the publish workflow itself was not modified. SV0’s evidence pack would name every OIDC Trusted Publisher binding the organization holds (on npm, PyPI, RubyGems, container registries), list the workflow, branch, and environment constraints its policy demands, and tie each binding to the inventory of cache-restoring jobs reachable from fork-triggered workflows. The pre-exfiltration question the pack answers is which publish-capable workflows restore cache entries written by untrusted PR jobs, and which of those jobs can reach id-token: write before cache integrity is proven. The post-exfiltration question is which @tanstack/*, Mistral AI, Guardrails AI, UiPath, or other affected versions were resolved on any of our CI runners or developer workstations between 2026-05-11 19:20 UTC and the quarantine window, and which AWS/GCP metadata-API calls, Vault reads, or GitHub token uses originated from those hosts in the same window.
The continuity with the 2026-05-01 Lightning post is the load-bearing point. That post named PyPI Trusted Publishing as the replacement for long-lived publish tokens. The May 11 wave didn’t break that advice — it sharpened it. A Trusted Publisher binding is itself a non-human identity, and like any NHI it needs explicit held-vs-exercised scope binding: a specific workflow file, a protected branch, an isolated build context, cache provenance, and ideally an environment-gated approval before publication authority is minted. The secondary angle is unproven_execution (ASI05): the release workflow restored, and then executed, cache contents that no maintainer ever sanctioned — the same authorization-envelope failure the Langflow CSV Agent + Python REPL post described, displaced one layer down into the CI build cache. SLSA provenance is necessary but not sufficient here: an attestation says “this artifact was produced by some run of TanStack’s release workflow”; it does not say “the run was triggered from main by a maintainer-approved merge, with cache contents whose hashes match a known-good restore.”
What To Do
- Bind every Trusted Publisher policy as tightly as the registry allows, then treat that as necessary but not sufficient. Require a specific workflow file, protected branch or tag pattern, and environment gate where supported. This blocks broad repo-scoped publish authority, but it would not by itself stop a poisoned cache from executing inside an otherwise legitimate release job.
- Disable or audit every
pull_request_targettrigger that writes toactions/cachekeys consumed by maintainer workflows. GitHub’s own guidance treatspull_request_targetas the most dangerous trigger. Combine it with cache-write privilege and any later workflow run that restores the same cache key is exposed to fork-controlled bytes. If the trigger is required, scope cache keys togithub.event.pull_request.head.shaand never let fork builds write to keys consumed bymain. - Verify or isolate restored cache entries before any publish-capable step runs. GitHub Actions cache restore returns bytes by key match alone; nothing in the protocol attests to the contents. Use dependency installs from lockfiles, immutable artifact stores, or maintainer-produced cache manifests for release jobs; do not let a cache written by an untrusted PR context execute before
id-token: writeis requested. - Treat any
id-token: writejob alongside publish privilege as a high-risk publication and gate it behind a GitHub Environment with required reviewers. The OIDCid-tokenlives in process memory for the duration of the request and is reachable from any binary in the runner image. An environment with required reviewers converts the implicit machine authority into a per-publish human approval and is enforced server-side rather than in the workflow file. - Inventory every dependency edge into
@tanstack/*, the Mistral AI SDK, Guardrails AI, UiPath, and the OpenSearch JS client across CI runners and developer workstations, and rotate every credential reachable from any host that resolved an affected version between 2026-05-11 19:20 UTC and the quarantine window. GitHub PATs, npm publish tokens, AWS/Azure/GCP SDK credentials, kubeconfigs, Vault tokens, and SSH keys are all in scope. On macOS workstations, search~/Library/LaunchAgents/for agh-token-monitor-named plist; on Linux, search systemd user and system units for the same name.
Sources
- NVD — CVE-2026-45321
- GHSA-g7cv-rxg3-hmpx — TanStack/router GitHub Security Advisory
- TanStack — npm Supply Chain Compromise Postmortem
- TanStack — Incident Followup and Hardening
- StepSecurity — Mini Shai-Hulud Is Back
- Endor Labs — How a Misconfigured CI Workflow Became an npm Supply Chain Compromise
- Orca Security — TanStack npm Supply Chain Worm
- Snyk — TanStack npm Packages Compromised
- Wiz — Mini Shai-Hulud Strikes Again
- The Hacker News — Mini Shai-Hulud Worm Compromises TanStack, Mistral AI, Guardrails AI
- BleepingComputer — Shai-Hulud Attack Ships Signed Malicious TanStack, Mistral npm Packages
- GitLab Advisories — @tanstack/react-router CVE-2026-45321
- Microsoft Security Blog — Trivy Supply Chain Compromise (March 2026)
- SecurityWeek — Bitwarden npm Package Supply Chain Attack (April 2026)
- MITRE ATT&CK: T1195.002, T1528, T1552.005, T1543.001, T1485