TanStack npm Supply Chain Attack: How It Happened

Khanh Nguyen
Khanh Nguyen
(Updated: )
The three-step supply chain attack

In six minutes on May 11, 2026, a threat group identified as TeamPCP published 84 malicious versions of 42 @tanstack/* packages to npm — not by stealing a developer's credentials, but by chaining three individually documented GitHub Actions weaknesses into a single, nearly invisible attack path that produced packages with valid SLSA provenance.

The Three-Stage Attack Chain That Bypassed Trusted Publishing

The compromise, detailed in TanStack's official postmortem and the GitHub security advisory GHSA-g7cv-rxg3-hmpx, required no persistent foothold and left almost no artifacts in node_modules. Each stage enabled the next.

Stage 1 — Entry via pull_request_target misconfiguration. The bundle-size.yml workflow used a pull_request_target trigger, which allows workflows initiated by fork pull requests to run with the permissions of the base repository rather than the fork. An attacker-controlled fork PR was sufficient to execute arbitrary code with elevated access. This trigger class is widely documented as dangerous when combined with checkout of untrusted code, yet it remains common in repositories that measure bundle size on contributor PRs.

Stage 2 — Cache poisoning. With workflow execution rights in the base repository context, the attacker injected a malicious vite_setup.mjs into the Linux-pnpm-store-* GitHub Actions cache key. Cache poisoning is effective here for a structural reason: cached artifacts are restored early in subsequent workflow runs, before most security controls or secret guards are applied. The poisoned entry would persist silently until a legitimate maintainer triggered a release.

Stage 3 — OIDC JWT extraction and impersonation. When a TanStack maintainer triggered a legitimate release from the main branch, the poisoned cache was restored as part of normal workflow setup. The malicious vite_setup.mjs then performed a runtime memory dump of the Actions runner process to extract the ephemeral OIDC JSON Web Token. That token — issued by GitHub's OIDC provider for trusted publishing to npm — was used to publish the malicious packages under the @tanstack scope with valid provenance attestations. The npm registry had no basis to reject the publication.

This is a meaningful design tension in the trusted-publisher model: eliminating long-lived npm tokens reduces one class of credential theft, but it concentrates trust in the ephemeral OIDC JWT. If that token is readable from runner memory during the same job that restores a poisoned cache, the provenance guarantee holds cryptographically while the underlying build environment has already been compromised. The StepSecurity analysis attributes this attack chain to TeamPCP; that attribution has not been independently confirmed by a government or law-enforcement body as of the time of this writing.

The chart below maps all three stages, their dependency on each other, and the artifacts each stage produced or consumed.

TanStack Triple Chain Attack: Three-Stage CI/CD Compromise Pipeline A flow diagram showing how pull_request_target abuse, GitHub Actions cache poisoning, and OIDC JWT hijacking were chained to publish malicious @tanstack packages with valid SLSA provenance. {"chartType":"pipeline-flow","title":"TanStack Triple Chain Attack Pipeline","summary":"Three-stage attack: fork PR abuse enables cache poisoning; poisoned cache enables OIDC JWT extraction; stolen OIDC JWT enables malicious npm publish with valid provenance.","data":[{"stage":1,"label":"pull_request_target Abuse","artifact":"Workflow exec in base repo context"},{"stage":2,"label":"Cache Poisoning","artifact":"Malicious vite_setup.mjs in Linux-pnpm-store-* key"},{"stage":3,"label":"OIDC JWT Hijack","artifact":"Memory dump extracts OIDC token → npm publish with valid provenance"}]} Triple Chain Attack Pipeline — TanStack npm Compromise Source: TanStack postmortem · GitHub advisory GHSA-g7cv-rxg3-hmpx · StepSecurity analysis STAGE 1 — ENTRY pull_request_target misconfiguration bundle-size.yml STAGE 2 — PERSISTENCE Actions Cache Poisoning vite_setup.mjs injected Linux-pnpm-store-* key STAGE 3 — ESCALATION OIDC JWT Extraction Runner memory dump → npm publish w/ provenance ARTIFACT PRODUCED Elevated workflow execution in base repo context ARTIFACT PRODUCED Poisoned cache entry awaiting legitimate release trigger ARTIFACT PRODUCED 84 malicious package versions with valid SLSA provenance Each stage was required; removing any one would have broken the chain TeamPCP attribution per StepSecurity — not independently confirmed by government or law enforcement

Six Minutes, 84 Packages: The Incident Timeline

The publication window was narrow. According to the TanStack postmortem and community reports, the first batch of 42 malicious packages appeared at 19:20 UTC on May 11, 2026. A second batch of the same 42 packages — this time tagged as latest on npm — was published at 19:26 UTC. Setting the latest tag is significant: it means any project running npm install @tanstack/<package> without a pinned version would have silently pulled a malicious build.

At 19:30 UTC, a community member filed GitHub Issue #7383, triggering an escalating response. By 20:15 UTC, TanStack maintainers had acknowledged the compromise and begun deprecating affected versions. The npm security team was engaged by 21:00 UTC to pull the affected tarballs.

The payload itself was designed to avoid detection during routine inspection. The primary file, router_init.js, weighed approximately 2.3 MB of obfuscated JavaScript — large enough to obscure its structure but not anomalous for a bundled JavaScript artifact. The file was placed at the package root and deliberately omitted from the files array in package.json, rendering it invisible to most automated inspection tools that audit declared package contents. A secondary helper, tanstack_runner.js, assisted execution. After completing its work, the payload exited with code 1 to suppress errors and avoid leaving artifacts in node_modules that a post-install audit might flag.

One fingerprint that survived this cleanup: a fake optionalDependencies entry in package.json pointing to github:tanstack/router#79ac49eedf774dd4b0cfa308722bc463cfe5885c — an orphan commit on an attacker-controlled fork. This reference is now a reliable indicator of compromise in any package inspection.

The timeline below shows the six-minute publication window and the subsequent community-driven response.

TanStack Incident Timeline — May 11, 2026 UTC A horizontal timeline showing five key events from the first malicious npm publish at 19:20 UTC through maintainer acknowledgment at 20:15 UTC and npm security engagement at 21:00 UTC. {"chartType":"horizontal-timeline","title":"TanStack Incident Timeline — May 11 2026 UTC","summary":"84 malicious packages were published across two batches in six minutes; community detection and maintainer response followed within 50 minutes.","data":[{"time":"19:20","event":"Batch 1 published (42 packages)"},{"time":"19:26","event":"Batch 2 published as 'latest' (42 packages)"},{"time":"19:30","event":"Community report: GitHub Issue #7383"},{"time":"20:15","event":"Maintainers acknowledge; begin deprecating affected versions"},{"time":"21:00","event":"npm security team engaged; tarballs pulled"}]} Incident Timeline — May 11, 2026 (UTC) Source: TanStack postmortem · GitHub Issue #7383 19:20 19:26 19:30 20:15 21:00 Batch 1 published 42 packages Batch 2 published 42 pkgs as 'latest' Community report Issue #7383 Maintainers begin deprecating versions npm security team pulls tarballs 6-min publish window — attacker controlled ~90-min community + maintainer response Both package batches used valid SLSA provenance; npm had no automated basis to reject publication

Payload Behavior: Worm Logic, Encrypted Exfiltration, and a Dead-Man's Switch

Three payload behaviors distinguish this incident from a straightforward credential-stealing attack and raise the consequence level for any affected maintainer.

Worm propagation. Upon execution, router_init.js queried npm for all packages associated with maintainer:<victim> and attempted to re-publish each one with the same injection. A developer who maintains five packages becomes a vector for infecting all five. The GitHub security advisory confirms this behavior; the full scope of secondary infections, if any occurred, has not been disclosed at the time of publication.

Exfiltration via Session network. Stolen credentials — including AWS and GCP metadata service tokens, Kubernetes service account tokens, HashiCorp Vault tokens, ~/.npmrc contents, GitHub tokens, and SSH private keys — were exfiltrated using the Session messenger network (filev2.getsession.org, seed1.getsession.org, seed2.getsession.org). Session uses end-to-end encryption and does not expose a traditional command-and-control IP address. This means standard network-layer blocking of a C2 IP is not a viable defensive response; defenders must treat the exfiltration as complete for any environment where the payload executed, and rotate all secrets accordingly.

Dead-man's switch. The payload included a script, gh-token-monitor.sh, that continuously polled the stolen GitHub token's validity. If GitHub returned an HTTP 40x response — indicating the token had been revoked — the script triggered rm -rf ~/. on the victim's machine. This behavior means that revoking a stolen token without first preserving the affected environment for forensic review could trigger data destruction on the developer's local system. Defenders should plan their token rotation sequence carefully: isolate the machine, preserve logs, then rotate.

The data exfiltrated is of the class that enables persistent downstream access: cloud metadata tokens can be used to assume IAM roles; Vault tokens may expose secrets far beyond the npm ecosystem; SSH keys may provide access to internal infrastructure. Any organization whose developers or CI systems installed affected package versions should assume full secret compromise, not just npm credential compromise.

The reference card below lists every confirmed indicator of compromise from the TanStack postmortem and StepSecurity analysis for use in log review and endpoint inspection.

Indicators of Compromise — TanStack npm Attack May 2026 A reference card listing confirmed indicators of compromise across four categories: malicious git ref, exfiltration domains, hostile persistence scripts, and data classes targeted for theft. {"chartType":"ioc-reference-card","title":"Indicators of Compromise — TanStack npm Attack","summary":"Four IoC categories: malicious git ref in package.json optionalDependencies; Session messenger exfil domains; gh-token-monitor.sh persistence; stolen credential classes including AWS/GCP tokens, Vault tokens, SSH keys, npmrc, GitHub tokens.","data":[{"category":"Malicious Git Ref","indicator":"github:tanstack/router#79ac49eedf774dd4b0cfa308722bc463cfe5885c"},{"category":"Exfil Domains","indicator":"filev2.getsession.org / seed1.getsession.org / seed2.getsession.org"},{"category":"Persistence","indicator":"~/.local/bin/gh-token-monitor.sh / com.user.gh-token-monitor LaunchAgent"},{"category":"Data Targeted","indicator":"AWS/GCP metadata, K8s tokens, Vault tokens, ~/.npmrc, GitHub tokens, SSH private keys"}]} Indicators of Compromise — TanStack npm Attack, May 2026 Source: TanStack postmortem · StepSecurity analysis · GitHub advisory GHSA-g7cv-rxg3-hmpx MALICIOUS GIT REF optionalDependencies in package.json → orphan commit EXFIL DOMAINS filev2.getsession.org seed1/seed2.getsession.org PERSISTENCE ~/.local/bin/gh-token-monitor.sh com.user.gh-token-monitor (macOS) DATA TARGETED AWS/GCP · K8s · Vault tokens SSH keys · npmrc · GitHub tokens MALICIOUS GIT REF (full): github:tanstack/router#79ac49eedf774dd4b0cfa308722bc463cfe5885c Appears in package.json optionalDependencies as @tanstack/setup — not visible via standard 'files' audit DEAD-MAN'S SWITCH: gh-token-monitor.sh polls stolen GitHub token validity. HTTP 40x response triggers rm -rf ~/. Rotate token only after isolating and preserving the affected machine for forensic review. Remediation: pin to patched versions (e.g. react-router@1.169.9) · rotate all secrets from affected environments · audit Cloud and GitHub Actions logs from May 11 2026

What Defenders Should Check Now

The remediation steps documented in the TanStack security advisory have three components that should be executed in a deliberate sequence.

Update first. Move to patched versions. The advisory identifies react-router@1.169.9 as an example patched release; check the advisory for the full list of affected and patched versions across all 42 @tanstack/* packages.

Preserve before revoking. Because the dead-man's switch destroys local data on token revocation, any developer or CI system that may have installed an affected version should isolate the potentially compromised machine and preserve a copy of GitHub Actions logs, Cloud audit logs, and shell history before touching credentials. Token revocation should follow isolation, not precede it.

Rotate broadly. Any secret accessible from an environment where an affected package executed should be treated as compromised: AWS and GCP credentials, Kubernetes service account tokens, HashiCorp Vault tokens, SSH keys, ~/.npmrc contents, and GitHub personal access tokens or fine-grained tokens. The scope of exfiltration via the Session network cannot be confirmed or bounded after the fact, so rotation should be treated as mandatory rather than precautionary.

For CI systems, audit GitHub Actions workflow runs from May 11, 2026 for any job that restored the Linux-pnpm-store-* cache and subsequently ran a release pipeline. Review Cloud audit logs for any API calls from Actions runner IPs around 19:20–21:00 UTC on that date.

For repository maintainers, the StepSecurity analysis recommends replacing pull_request_target triggers with pull_request in any workflow that does not explicitly require base-repository secrets, and pinning GitHub Actions cache restore steps to hashed action versions to prevent cache poisoning from surviving across workflow runs.

Comments (0)

No comments yet.

Be the first to share your perspective on this topic.