Mini Shai-Hulud Wave 4: How TeamPCP Broke SLSA, Poisoned TanStack, and Turned the Developer Supply Chain Into a Worm Farm
TL;DR: On May 11, 2026 between 19:20–19:26 UTC, threat group TeamPCP published 84 malicious npm artifacts across 42 @tanstack/* packages — not by stealing credentials, but by hijacking TanStack's own trusted release pipeline and extracting an OIDC token at runtime. The attack was the fourth and most sophisticated wave in the Shai-Hulud supply chain worm campaign, and the first to produce npm packages carrying valid SLSA Build Level 3 attestations — a property previously treated as a strong trust signal. Within hours, the worm self-propagated to Mistral AI, UiPath, OpenSearch, Guardrails AI, and over 100 additional maintainers across npm and PyPI, producing 404 total malicious versions. Every developer who ran npm install against an affected package during the window must treat their install host as fully compromised and rotate all reachable secrets immediately.
Background: The Shai-Hulud Campaign Timeline
May 11's attack did not emerge in a vacuum. It is wave four of a persistent, escalating campaign that has spent nine months systematically advancing the state of the art in open-source supply chain compromise.
Wave 1 — September 2025: The original Shai-Hulud worm compromised 500+ npm packages across 700+ repositories in a 48-hour window. Its innovation was self-propagation: after a successful install-hook execution, the worm enumerated other packages the victim maintained and injected itself into their next publish. The harvest mechanism relied on TruffleHog patterns for secret extraction. CISA issued a public advisory.
Wave 2 — November 2025: Shai-Hulud 2.0 expanded to 492 packages totaling 132M monthly downloads and 25,000+ dependent repositories. The preinstall hook eliminated the need for any human interaction post-install. A home-directory destruction fallback was added — if exfiltration failed, the worm would wipe ~/ to erase forensic evidence. The Trigger.dev team published a detailed postmortem that documented how months of build artifacts had been corrupted before detection.
Wave 3 — April 29, 2026: Mini Shai-Hulud pivoted to the SAP and Intercom npm namespaces. This wave introduced two critical new capabilities: first, AI coding agent persistence — the worm wrote .claude/settings.json and .claude/setup.mjs to the victim's filesystem, establishing a foothold inside Claude Code's auto-trust zone; second, encrypted exfiltration — credential harvest data was encrypted before transmission, defeating plaintext DLP rules. A Russian locale exemption was also added, consistent with attribution to a Russia-adjacent threat actor.
Wave 4 — May 11, 2026 (this incident): The TanStack attack introduced two new landmark capabilities that fundamentally challenge the current supply chain trust model: OIDC token runtime extraction and valid SLSA Build Level 3 provenance on malicious packages.
TeamPCP is publicly tracked under aliases DeadCatx3, PCPcat, ShellForce, and CipherForce. Palo Alto's Unit 42 documented the group's announced partnership with the Vect ransomware collective on BreachForums — a partnership that suggests the credential harvest from each wave likely feeds into downstream ransomware targeting.
Technical Analysis: A Three-Stage Attack That Took Eight Months to See
TanStack's official postmortem is unusually transparent and technically detailed. The attack chains three independently-known weaknesses that, combined, defeat an artifact supply chain's most respected defense layers.
Stage 1: The Pwn Request and Cache Poisoning (May 10–11, pre-attack)
On May 10, the attacker registered a fork of TanStack/router under a renamed repository (zblgg/configuration) — deliberately chosen to avoid appearing in GitHub's standard fork list view, which only shows forks of the same name. A fabricated identity claude <[email protected]> was used for the poisoning commit — a notable detail that appears designed to blend into modern developer tooling signatures.
The malicious commit 65bf499d was authored with a [skip ci] prefix to suppress CI on the push event, then embedded a ~30,000-line obfuscated JavaScript payload inside packages/history/vite_setup.mjs. On May 11 at 10:49 UTC, a pull request was opened against TanStack/router#main with the innocuous title "WIP: simplify history build."
The critical vulnerability was in bundle-size.yml and labeler.yml — two GitHub Actions workflows using the pull_request_target event type. Unlike pull_request, which runs with read-only permissions and requires maintainer approval for first-time contributors, pull_request_target runs in the context of the base repository with its full secrets and permissions. This pattern — known as the "Pwn Request" — has been documented since 2021 but persists throughout the npm ecosystem.
Between 11:01–11:31 UTC, the attacker made multiple force-pushes to land the malicious commit on the PR head. When bundle-size.yml's benchmark job ran, it executed pnpm install + pnpm nx run @benchmarks/bundle-size:build — which executed vite_setup.mjs with full Actions runner privileges. The workflow completed, and as a side effect, saved a 1.1 GB cache entry keyed Linux-pnpm-store-6f9233a50def742c09fde54f56553d6b449a535adf87d4083690539f49ae4da11 scoped to refs/heads/main in the TanStack/router repository. At 11:31 UTC, the attacker force-pushed the PR back to a zero-diff state and closed it. Cache poison in place; no visible trace in the codebase.
Stage 2: OIDC Token Extraction and Publish Hijack (May 11, 19:15–19:26 UTC)
GitHub Actions OIDC trusted publishing is designed to eliminate long-lived npm tokens. When release.yml runs on a push to main, it is authorized by npm's registry to publish under the @tanstack namespace via a Sigstore-generated OIDC attestation — no static token required. The implicit security assumption is that only the workflow's defined publish steps can mint and use this token.
That assumption is incorrect.
When legitimate PRs #7369 and #7382 were merged to main at 19:15 and 19:16 UTC, release.yml triggered. Both runs restored the poisoned pnpm cache saved eight hours earlier. During the test and cleanup phases — which ran even though the defined Publish step was eventually skipped due to test failures — the worm payload executed. It called GitHub's OIDC token endpoint (/id-token) directly from within the workflow runner process, minted a fresh attestation, and used it to authenticate a direct POST to registry.npmjs.org, bypassing the workflow's publish step entirely.
Between 19:20 and 19:26 UTC, 84 npm package versions were published across 42 packages — all cryptographically signed with valid Sigstore attestations, all carrying valid SLSA Build Level 3 provenance, all linking back to TanStack/router's legitimate repository and branch ref.
SLSA did not fail because its implementation was broken. It failed because it correctly verified that the package was built inside the trusted pipeline — it cannot verify that the pipeline itself had not been subverted.
Both workflow runs completed with status failure. The test failures that caused the Publish step to be skipped are what actually prevented the packages from reaching consumers with intended routing fixes — while the malicious packages went out anyway.
Stage 3: Self-Propagation and Credential Harvest
The router_init.js payload (~2.3 MB, obfuscated) injected into every affected tarball executes on npm install via an optionalDependencies trick that invokes a prepare lifecycle script. It performs four actions simultaneously:
1. Credential harvest across AWS IMDS (169.254.169.254), GCP metadata server, Kubernetes service-account tokens (/var/run/secrets/kubernetes.io/serviceaccount/token), HashiCorp Vault (localhost:8200), ~/.npmrc, GitHub environment tokens (scanned for ghp_, gho_, ghs_, npm_ patterns), SSH private keys, and git credential stores.
2. Exfiltration via Session/Oxen — credentials are uploaded to filev2.getsession.org and seed{1,2,3}.getsession.org, which are the file transfer endpoints for the Session decentralized messaging network. All traffic is end-to-end encrypted at the protocol layer, which means standard DLP solutions inspecting TLS traffic see encrypted blobs with no plaintext credential indicators. IP/domain blocklisting of getsession.org is the only network-layer mitigation.
3. Persistence via AI coding agent config — following the Wave 3 innovation, the worm writes .claude/settings.json, .claude/setup.mjs, .vscode/tasks.json, and .vscode/setup.mjs to the install directory. These files establish a persistent foothold that will re-execute on the next Claude Code or VS Code session in that workspace, even after the malicious npm package has been deprecated and cleaned.
4. Self-propagation — the worm calls registry.npmjs.org/-/v1/search?text=maintainer:<victim> to enumerate every package the install-host's npm credentials can publish to, then republishes them with the injected payload. This is how the initial TanStack breach within hours reached Mistral AI's npm SDK suite (3 packages, 9 versions), UiPath's RPA runtime (65 packages), OpenSearch's JavaScript client (1.3M weekly downloads), and over 100 additional maintainers — totaling 404 malicious versions across npm and PyPI by the time containment was achieved.
The PyPI expansion used a different payload delivery mechanism: the mistralai==2.4.6 and guardrails-ai==0.10.1 packages did not execute on pip install but on import. At runtime, a Python dropper fetches transformers.pyz from hxxps://git-tanstack[.]com/transformers.pyz (a lookalike domain registered May 9, two days before the attack) and executes it with python3. This means sandboxed install environments — pip install --dry-run, virtual environments with restricted filesystem access, dependency scanning that only inspects install-time scripts — will not catch it. The trigger is the first import mistralai in application code.
Indicators of Compromise
| Type | Value | Notes |
|------|-------|-------|
| Domain | filev2[.]getsession[.]org | Session C2 exfil endpoint |
| Domain | seed{1,2,3}[.]getsession[.]org | Session seed nodes |
| Domain | git-tanstack[.]com | PyPI secondary C2 (registered 2026-05-09) |
| URL | hxxps://git-tanstack[.]com/transformers[.]pyz | PyPI stage-2 payload |
| URL | hxxp://169[.]254[.]169[.]254/latest/meta-data/iam/security-credentials/ | AWS IMDS probe |
| URL | hxxp://127[.]0[.]0[.]1:8200 | Vault probe |
| File | .claude/settings.json | AI agent persistence hook |
| File | .claude/setup.mjs | AI agent persistence payload |
| File | .vscode/tasks.json | VS Code persistence hook |
| File | .vscode/setup.mjs | VS Code persistence payload |
| File | /tmp/transformers.pyz | PyPI stage-2 payload drop path |
| Git commit | 65bf499d16a5e8d25ba95d69ec9790a6dd4a1f14 | Poisoning commit on zblgg/configuration fork |
| Git commit | 79ac49eedf774dd4b0cfa308722bc463cfe5885c | Payload host on tanstack/router fork network |
| npm SHA-256 | ce7e4199506959fd7a71b64209b2c07b9c82e53a946aa7d78298dc9249230d01 | @mistralai/[email protected] malicious version |
| GitHub identity | claude <[email protected]> | Fabricated commit identity |
| CVE | CVE-2026-45321 | GHSA-g7cv-rxg3-hmpx |
Confirmed-clean TanStack families: @tanstack/query, @tanstack/table, @tanstack/form, @tanstack/virtual, @tanstack/store, @tanstack/start (meta-package only, not @tanstack/start-*).
The SLSA Problem: Provenance Certifies the Pipeline, Not the Code
This attack deserves extended discussion because it strikes at a foundational principle in the supply chain security movement. SLSA (Supply-chain Levels for Software Artifacts) Build Level 3 is the current gold standard for provenance: it cryptographically attests that a package was built inside a designated, trusted pipeline, from a specific source repository and ref. The Open Source Security Foundation, CISA, and the major cloud providers have all pointed to SLSA as a key mitigation for supply chain attacks.
TeamPCP's Wave 4 demonstrates a clean bypass: if you can execute arbitrary code inside the trusted pipeline — through a cache-poisoning attack on GitHub Actions — you inherit the pipeline's OIDC identity. Sigstore will issue you a valid attestation. npm's trusted publisher system will accept the publish. Every downstream consumer checking SLSA provenance will see a green checkmark.
The trust model assumes the build environment is clean. The build environment is not clean if its dependency caches are attacker-controlled.
This is not an argument that SLSA is worthless — provenance attestations remain valuable for a wide range of attack vectors. But it does mean that SLSA Build Level 3 cannot be treated as a strong signal that a package is safe if it was published during or after a cache poisoning event. The field needs to extend provenance to cover cache integrity and workflow step isolation, not just source-to-artifact lineage.
Lyrie Take
Wave 4 is a systems-thinking attack. No individual weakness here was novel: Pwn Requests have been documented since 2021; OIDC token extraction from Actions runners was documented in the March 2025 tj-actions incident; GitHub Actions cache poisoning has been in scope for security researchers for years. What TeamPCP did was chain all three into a single, multi-day attack sequence that completes in a 6-minute publish window — and then demonstrated that the trust artifact most defenders cite as a supply chain mitigation (SLSA provenance) is bypassed in the same stroke.
The self-propagation mechanic is particularly concerning at scale. The worm doesn't need to attack high-value targets directly. It attacks the developers who maintain packages used by high-value targets. Every CI/CD pipeline that ran npm install against an affected package during the window is a potential lateral movement vector into its host organization. For enterprises running UiPath RPA workflows, the blast radius is enterprise automation infrastructure with broad filesystem and credential access by design.
The AI coding agent persistence vector — now in its second wave — also warrants attention beyond this incident. An attacker who can reliably write to .claude/settings.json has established a foothold that survives package cleanup, npm cache purges, and in most cases, developer awareness. Claude Code's auto-trust model for project settings files assumes the settings file was authored by the developer. That assumption is no longer safe in any environment that has run an infected npm package.
The credential rotation requirement from this incident is not optional. If any developer on your team ran npm install on a Node.js project between 19:15 and 21:00 UTC on May 11, 2026, and did not have lockfile pinning that would have prevented the affected versions from being resolved, treat the install host as fully compromised.
Defender Playbook
Immediate (if potentially affected)
1. Identify all hosts that ran npm install / pnpm install / yarn install between 19:15–21:00 UTC on 2026-05-11
2. Rotate immediately: AWS IAM credentials, GCP service account keys, Kubernetes service account tokens, Vault tokens, npm access tokens (npm token revoke --token=<token>), GitHub PATs/OAuth tokens, SSH private keys accessible from the install host
3. Check for and delete .claude/settings.json, .claude/setup.mjs, .vscode/tasks.json, .vscode/setup.mjs in all project directories on the install host
4. Check for and delete /tmp/transformers.pyz on any host that has imported mistralai or guardrails-ai after May 11
5. Audit GitHub Actions caches across all your repositories (gh api /repos/{owner}/{repo}/actions/caches) and purge entries created between May 10–12
GitHub Actions hardening
6. Audit all pull_request_target workflows — any that checkout PR code or run it directly are vulnerable to Pwn Request; restructure to use the pull_request event or add explicit repository_owner guards
7. Pin all third-party action references to SHA digests, not tags or branches
8. Add permissions: {} at the workflow level and elevate only the specific permissions required per-job (id-token: write should be restricted to publish jobs only, not test jobs)
9. Enable GitHub Actions cache isolation: ensure save-state and restore-state steps specify explicit keys that cannot be pre-populated by fork workflows
10. Set ACTIONS_RUNNER_DEBUG=true in your repository secrets — this will log OIDC token minting events, making hijack detection easier
Ongoing supply chain controls
11. Implement lockfile enforcement (npm ci, pnpm install --frozen-lockfile) in all CI pipelines — this prevents worm-published versions from resolving even if the registry is poisoned
12. Enable npm package provenance verification (npm audit signatures) — while this incident demonstrated SLSA bypass, it still blocks unsigned packages
13. Add getsession.org and git-tanstack.com to egress blocklists (network layer, proxy policy, and DNS sinkhole)
14. Consider adopting SafeDep, Snyk, or similar SCA tooling with real-time registry monitoring — detection in this incident was by external researchers within 20 minutes, not by npm's own systems
15. Treat AI coding agent config files (.claude/, .cursor/, .github/copilot-instructions.md) as high-privilege attack surface — review them in code review like you would review .github/workflows/
Sources
- TanStack Official Postmortem (2026-05-11): https://tanstack.com/blog/npm-supply-chain-compromise-postmortem
- Snyk Technical Analysis: https://snyk.io/blog/tanstack-npm-packages-compromised/
- SafeDep Incident Report: https://safedep.io/mass-npm-supply-chain-attack-tanstack-mistral/
- StepSecurity Attribution Report: https://www.stepsecurity.io/blog/mini-shai-hulud-is-back-a-self-spreading-supply-chain-attack-hits-the-npm-ecosystem
- Wiz Blog — Wave 4 Analysis: https://www.wiz.io/blog/mini-shai-hulud-strikes-again-tanstack-more-npm-packages-compromised
- GitHub Security Advisory GHSA-g7cv-rxg3-hmpx: https://github.com/TanStack/router/security/advisories/GHSA-g7cv-rxg3-hmpx
- CVE: CVE-2026-45321
- CISA Advisory (Wave 1): https://www.cisa.gov/news-events/alerts/2025/09/23/widespread-supply-chain-compromise-impacting-npm-ecosystem
Lyrie.ai Cyber Research Division — Senior Analyst Desk
Lyrie Verdict
Lyrie's autonomous defense layer flags this class of exposure the moment it surfaces — no signature update required.