On May 11, 2026, TanStack maintainers confirmed that 42 @tanstack packages had been hijacked and republished with a malicious payload — including @tanstack/react-router, which pulls more than 12 million downloads a week. The attack, a second wave of the self-propagating "Mini Shai-Hulud" npm worm, is attributed to a threat actor tracked as TeamPCP. It used a chained GitHub Actions technique — a "Pwn Request" pattern combined with Actions cache poisoning and runtime memory extraction of an OIDC token from a live CI runner — to mint legitimate npm publish credentials and even valid SLSA Build Level 3 provenance attestations for trojanized packages. By the end of the day, the worm had spread to 373 malicious versions across 169 npm packages and several PyPI projects tied to Mistral AI, OpenSearch, and Guardrails AI, harvesting AWS, GitHub, npm, Vault, and Kubernetes credentials from every machine that installed a poisoned version.
What is Mini Shai-Hulud, and how did it get into TanStack's release pipeline?
Mini Shai-Hulud is a self-propagating npm worm that spreads by stealing the CI/CD credentials of one maintainer and using them to publish malicious versions of that maintainer's other packages. Per TanStack's own postmortem, the intrusion began with a "Pwn Request": an attacker-controlled pull request exploited a workflow trigger on pull_request_target, which runs with access to repository secrets even for untrusted fork contributions. The attacker then poisoned the GitHub Actions cache across the fork-to-base trust boundary and, critically, read process memory of the Runner.Worker process to extract a live GitHub Actions OIDC token before it expired. That token was exchanged for short-lived npm publish credentials scoped to TanStack's own release pipeline, so the malicious versions went out through TanStack's legitimate CI, not a spoofed account. StepSecurity traced the intrusion to a GitHub account named voicproducoes (account ID 269549300, created March 19, 2026), which forked the TanStack repo on May 10, 2026 and pushed commit 79ac49eedf774dd4b0cfa308722bc463cfe5885c.
How many packages and downloads did this actually touch?
The initial wave hit 84 malicious versions across 42 @tanstack packages, including @tanstack/react-router, @tanstack/router-core, and @tanstack/vue-router. Socket.dev's monitoring flagged the first compromised publish within six minutes, but the worm's self-propagation logic kept enumerating and republishing under other compromised maintainer accounts throughout the day. By May 12 at 03:05:38 UTC, the campaign had expanded to @opensearch-project/opensearch (versions 3.5.3, 3.6.2, 3.7.0, and 3.8.0), PyPI's mistralai 2.4.6, PyPI's guardrails-ai 0.10.1, @squawk/*, and over 60 UiPath packages. Total tally by end of day: 373 malicious versions across 169 npm packages plus several PyPI projects — a footprint that, because @tanstack/react-router alone sees 12+ million weekly pulls, means the compromised code touched development and CI environments at a scale most single-vendor breaches never reach.
How did malicious packages end up with valid SLSA Build Level 3 provenance?
They got valid attestations because the attacker stole a real OIDC token from GitHub's own signing infrastructure rather than forging one. SLSA provenance and Sigstore attestations work by having the build system itself vouch for what it produced — the attestation service trusts the identity presented by the token, and TeamPCP's memory-scraping technique against the Runner.Worker process handed them a token that was, cryptographically, indistinguishable from a legitimate TanStack maintainer's release credential. StepSecurity and Snyk both flagged this as the first documented npm worm to produce validly-attested malicious packages, which matters because it breaks a core assumption of provenance-based trust: teams that gate installs on "does this package have SLSA L3 attestation" would have let every one of the 84 compromised TanStack artifacts through. Provenance proves who built a package, not whether that builder's credentials were still theirs to use.
What does the router_init.js payload do once it lands on a machine?
Once installed via a prepare lifecycle hook triggered by a poisoned optionalDependencies entry, router_init.js — a 2.3 MB file wrapped in three obfuscation layers (an obfuscator.io string table of 11,516 encoded strings, a Fisher-Yates cipher with PBKDF2-SHA256 at 200,000 iterations, and AES-256-GCM payloads requiring the Bun runtime) — scans for over 100 categories of secrets. That includes AWS IMDSv2 credentials, GitHub and npm tokens, SSH keys, Kubernetes service-account tokens, HashiCorp Vault sessions, cryptocurrency wallets, and configuration files for AI coding tools like Claude Code and Kiro. It establishes persistence through .claude/settings.json and .claude/setup.mjs hooks, a .vscode/tasks.json entry that fires on folder open, a macOS LaunchAgent named gh-token-monitor, and a Linux systemd user service. Stolen data is exfiltrated over the Session messaging protocol's CDN at filev2.getsession.org, and the malware also drops dead-drop commits into victims' own repositories under the spoofed identity claude@users.noreply.github.com. A particularly aggressive detail: the npm token it steals is labeled with the description "IfYouRevokeThisTokenItWillWipeTheComputerOfTheOwner", meaning naive incident response — revoking the token before isolating the host — can trigger a destructive wipe routine.
Is TeamPCP a new actor, or have they done this before?
TeamPCP is not new — StepSecurity attributes the same group to the Aqua Security Trivy scanner compromise in March 2026 and the Bitwarden CLI npm package compromise in April 2026. That pattern points to a deliberate strategy of targeting developer- and security-tooling supply chains specifically, where a single compromised package gets pulled into thousands of CI pipelines and developer machines with elevated trust and broad credential access. The group's own defacement message, found on a domain registered as git-tanstack.com, read "We've been online over 2 hours now stealing creds" — a boast consistent with how fast the self-propagation loop worked: enumerate every package owned by a compromised maintainer via npm registry search, exchange the stolen GitHub OIDC token for a per-package publish token, and republish before defenders can react.
How do you check if you're affected, and what should you do first?
You check by diffing your lockfiles against the known-bad version list and hashing any suspicious router_init.js or tanstack_runner.js files against the published SHA-256 values (ab4fcadaec49c03278063dd269ea5eef82d24f2124a8e15d7b90f2fa8601266c and 2ec78d556d696e208927cc503d48e4b5eb56b31abc2870c2ed2e98d6be27fc96 respectively), and by looking for a tarball size anomaly — clean TanStack packages run around 190 KB, compromised ones balloon to roughly 905 KB. If you find a match, isolate the affected machine or runner first; do not revoke the npm token before isolation, since that's the wiper trigger. Then remove persistence artifacts in .claude/ and .vscode/, kill the gh-token-monitor LaunchAgent or systemd service, block egress to filev2.getsession.org, seed1.getsession.org, api.masscan.cloud, and git-tanstack.com, and rotate every credential class the malware targets — AWS, GitHub, npm, Vault, and Kubernetes service accounts — in that order, from most to least likely to have already been exfiltrated during the incident window.
How Safeguard Helps
Safeguard's reachability analysis would have flagged that a compromised @tanstack/react-router version was actually loaded and executed in your build and runtime paths, rather than just present in a lockfile, cutting through the noise of a 169-package advisory list to tell you which of your repos genuinely need action today. Griffin AI, Safeguard's investigation agent, correlates the malicious commit hash, IOC domains, and file hashes above against your CI logs and dependency graph automatically, so you don't have to manually grep for getsession.org egress or hand-check tarball sizes across every service. Safeguard continuously generates and ingests SBOMs so a worm-style advisory like this one resolves to an exact list of affected components in minutes, not a spreadsheet exercise. When a fix is available, Safeguard opens auto-fix pull requests that pin dependencies to clean versions and add lockfile integrity checks, closing the loop from detection to remediation without waiting on a manual triage cycle.