Snyk Blog In this article TanStack npm packages compromised: inside the Mini Shai-Hulud supply chain attack Wave four: campaign context What got hit How the attack worked: three chained vulnerabilities Step 1: Pwn Request via pull_request_target Step 2: GitHub Actions cache poisoning Step 3: OIDC token extraction from runner memory Inside the payload: router_init.js Three layers of obfuscation Daemonization Credential harvesting Exfiltration and dead drops Self-propagation Persistence: uninstall is not enough The SLSA provenance problem What to do right now Step 0: Determine if you are exposed Step 1: Contain persistence BEFORE rotating credentials Step 2: Rotate all secrets Step 3: Network-level mitigations Step 4: Audit GitHub Actions OIDC configuration Step 5: Audit pull_request_target workflows Step 6: Add release-age cooldowns Step 7: Do not trust SLSA provenance alone Snyk's coverage Summary: indicators of compromise TanStack Npm Packages Compromised Inside The Mini Shai Hulud Supply Chain Attack Written by Stephen Thoemmes May 11, 2026 0 mins read TanStack npm packages compromised: inside the Mini Shai-Hulud supply chain attack On May 11, 2026, between 19:20 and 19:26 UTC, 84 malicious npm package artifacts were published across 42 packages in the @tanstack namespace. The packages were not published by an attacker who stole credentials; they were published by TanStack's legitimate release pipeline, using its trusted OIDC identity, after attacker-controlled code hijacked the runner mid-workflow. The malicious versions spread to Mistral AI, UiPath, and dozens of other maintainers within hours. @tanstack/react-router alone receives over 12.7 million weekly downloads ( npm API ). The incident is attributed by StepSecurity to the threat group known as TeamPCP, and it is also the first documented case of a malicious npm package carrying valid SLSA provenance. SLSA provenance is a cryptographic certificate, generated by Sigstore, that is meant to verify a package was built from a trusted source. The worm was able to produce these certificates because it hijacked the legitimate build pipeline itself; Sigstore verified the build process correctly. What SLSA does not guarantee is that the code being built was safe. If your team installed any affected @tanstack/* version on May 11, treat the install environment as compromised and rotate every secret accessible from that host. Keep reading for the full package list, technical breakdown, and step-by-step remediation. CVE: CVE-2026-45321 | GHSA: GHSA-g7cv-rxg3-hmpx | Severity: Critical Wave four: campaign context The TanStack attack is not an isolated incident. It is the latest wave in a series of npm supply chain attacks using the Shai-Hulud worm toolchain. TeamPCP, the group StepSecurity attributes this attack to, is also responsible for compromising Aqua Security's Trivy scanner (March 2026), the Bitwarden CLI npm package (April 2026). The earlier Shai-Hulud waves (September and November 2025) used the same worm toolchain but were not attributed to TeamPCP. Wave Date Scale Key escalation Shai-Hulud Sep 14–16, 2025 500+ packages, 700+ repos First self-propagating npm worm; TruffleHog for secrets Sha1-Hulud 2.0 Nov 21–23, 2025 492 packages, 132M monthly downloads, 25,000+ repos preinstall hook (no human interaction); home-directory destruction fallback ( Trigger.dev postmortem ) Mini Shai-Hulud Apr 29, 2026 SAP/Intercom ecosystems First AI coding agent persistence ( .claude/settings.json ); encrypted exfil; Russian locale exemption Mini Shai-Hulud Is Back May 11, 2026 373 malicious versions, 169 packages First npm worm with valid SLSA Build Level 3 attestations; Session P2P C2 Each wave builds on the previous wave's technical sophistication. Wave four is notable not for its scale (Wave 2 was larger) but for what it achieved: publishing malicious packages that are indistinguishable from legitimate ones by provenance attestation, a property no prior supply chain attack had demonstrated. TeamPCP publicly took credit for the attack. The group is also tracked under the aliases DeadCatx3, PCPcat, ShellForce, and CipherForce. Unit 42 has documented the group's announced partnership with the Vect ransomware group, based on a BreachForums announcement. CISA issued advisories for both the original September 2025 wave and the tj-actions/changed-files compromise (March 2025), which first documented the OIDC token-extraction technique reused in this attack. What got hit The core TanStack router family was the initial vector, but the worm's self-propagation mechanism rapidly expanded the blast radius. TanStack packages (42 packages, 84 versions — two per package): Package Compromised versions @tanstack/react-router 1.169.5, 1.169.8 @tanstack/vue-router 1.169.5, 1.169.8 @tanstack/solid-router 1.169.5, 1.169.8 @tanstack/router-core 1.169.5, 1.169.8 @tanstack/react-start 1.167.68, 1.167.71 @tanstack/router-plugin 1.167.38, 1.167.41 The full 42-package list is in GHSA-g7cv-rxg3-hmpx and Snyk's Security Database . Confirmed-clean families: @tanstack/query* , @tanstack/table* , @tanstack/form* , @tanstack/virtual* , @tanstack/store . Secondary victims (worm-propagated): Namespace Example packages @mistralai @mistralai/mistralai 2.2.2–2.2.4; -azure and -gcp variants @uipath 40+ packages across the UiPath namespace @draftlab / @draftauth @draftlab/auth , @draftlab/db , @draftauth/client @squawk 19 aviation data packages misc. safe-action , cmux-agent-mcp , nextmove-mcp , ts-dna , cross-stitch , and more By end of day, at least 170 affected packages had been documented in Snyk's Security Database . @mistralai/mistralai is also registered in the OSSF malicious packages database as MAL-2026-3432 (GHSA-3q49-cfcf-g5fm). How the attack worked: three chained vulnerabilities The TanStack postmortem is thorough. Three vulnerabilities were chained; none alone would have been sufficient. Step 1: Pwn Request via pull_request_target On May 10, 2026, the attacker created a fork of TanStack/router under the account zblgg (GitHub ID 127806521), deliberately naming it zblgg/configuration to avoid appearing in fork-list searches. A malicious commit ( 65bf499d ) was authored under the fabricated identity claude <claude@users.noreply.github.com> impersonating the Anthropic Claude GitHub App, and prefixed with [skip ci] to suppress automated CI on push. On May 11 at 10:49, the attacker opened PR #7378 against TanStack/router#main , titled "WIP: simplify history build," and triggered a known misconfiguration: TanStack's bundle-size.yml workflow used the pull_request_target trigger but checked out the fork's merge ref and executed fork-controlled code: 1 on: 2 pull_request_target: 3 paths: ['packages/**', 'benchmarks/**'] 4 5 jobs: 6 benchmark-pr: 7 steps: 8 - uses: actions/checkout@v6.0.2 9 with: 10 ref: refs/pull/${{ github.event.pull_request.number }}/merge # fork code 11 12 - uses: TanStack/config/.github/setup@main # calls actions/cache@v5 13 14 - run: pnpm nx run @benchmarks/bundle-size:build # executes fork-controlled code This "Pwn Request" pattern was first documented and demonstrated against Angular, MDN, and hyperledger/besu by security researcher Adnan Khan in May 2024 . The pull_request_target trigger runs in the base repo's security context, so the fork's code had access to the base repo's cache scope and GITHUB_TOKEN . Step 2: GitHub Actions cache poisoning The malicious vite_setup.mjs from the fork did not exfiltrate data immediately. Instead, it poisoned the pnpm package store under the exact cache key that release.yml would later look up: 1 Linux-pnpm-store-6f9233a50def742c09fde54f56553d6b449a535adf87d4083690539f49ae4da11 This key was pre-computed from the public pnpm-lock.yaml using the same hashFiles('**/pnpm-lock.yaml') formula the workflow uses. The poisoned 1.1 GB cache entry was saved at 11:29 and persisted undetected for nearly eight hours, until a legitimate main branch push triggered release.yml at 19:15. The author of bundle-size.yml had attempted a trust split — keeping the benchmark job separate with a note about untrusted permissions — but missed a critical GitHub Actions behavior: actions/cache@v5 's post-job save uses a runner-internal token, not the workflow GITHUB_TOKEN , so permissions: contents: read does not prevent cache writes. Cache scope is also shared across pull_request_target runs and base-branch pushes, creating the cross-boundary poisoning window. Step 3: OIDC token extraction from runner memory release.yml holds id-token: write permission, which is required for npm's OIDC trusted-publisher binding. When attacker-controlled binaries from the poisoned pnpm store ran during the build phase, they used a technique documented in the tj-actions/changed-files compromise of March 2025 ; the TanStack postmortem notes the attacker used "the same memory-extraction technique (and verbatim Python script, with attribution comment)" from that incident: Locate the Runner.Worker process via /proc/*/cmdline Read /proc/<pid>/maps and /proc/<pid>/mem to dump the worker's address space Extract the OIDC token, which the runner mints lazily in memory when id-token: write is set POST directly to registry.npmjs.org authenticated as the legitimate TanStack release workflow The workflow's intended Publish Packages step was never reached, tests failed, and the step was skipped. The malicious publish happened anyway, authenticated with a valid OIDC token and valid SLSA provenance, because the attacker extracted the token before the workflow finished. Two release runs occurred. Both completed with status: failure . npm received 84 valid, signed, provenance-attested package publishes, run 25613093674 and 25691781302 . Inside the payload: router_init.js The 2.3 MB router_init.js file was smuggled into each compromised tarball's root, a file not declared in the package's files field, which proves the tarball was tampered with outside the normal build process. Each package also received an inject