critical
CVE
Not assigned
CWE
CWE-506
Affected Surface
@tanstack/react-router and 40+ TanStack packages, @mistralai/mistralai npm and PyPI packages, @uipath namespace (66 package-version entries), @opensearch-project/opensearch, guardrails-ai PyPI package, 169 total npm package names across @squawk, @tallyui, @beproduct, @draftlab, and others
On 11 May 2026, TeamPCP launched the largest wave yet of their “Mini Shai-Hulud” campaign — a coordinated supply-chain attack against the npm and PyPI ecosystems that compromised 373 malicious package-version entries across 169 npm package names and multiple PyPI packages. The highest-profile target was @tanstack/react-router, one of the most widely used routing libraries in the React ecosystem with approximately 12 million weekly downloads.
This is the same threat actor behind the earlier SAP npm compromise, the Checkmarx KICS GitHub Action attack, and the Trivy, Bitwarden, Lightning, and Intercom incidents. The operation has evolved from isolated package hijacks into a self-propagating worm that abuses trusted publishing infrastructure to spread through the npm ecosystem autonomously.
How the TanStack compromise worked
The attack exploited a chain of three vulnerabilities in GitHub Actions to gain publish access without ever stealing npm credentials directly.
Step 1 — Fork and pull request. The attacker created a fork of the TanStack/router repository (renamed to zblgg/configuration to evade fork-list searches), then opened a pull request against the upstream repo. The repository’s CI used a pull_request_target workflow, which checked out and executed the attacker’s fork code in a privileged context.
Step 2 — Cache poisoning. The attacker’s code poisoned the GitHub Actions cache with a malicious pnpm store. When legitimate maintainer PRs were later merged to main, the release workflow restored the poisoned cache, placing attacker-controlled binaries on the runner.
Step 3 — OIDC token theft from process memory. Those binaries extracted OIDC tokens directly from the GitHub Actions runner’s process memory (/proc/<pid>/mem). The attacker then used these OIDC tokens with npm’s trusted-publisher binding to mint valid short-lived npm publish tokens and push malicious package versions — all without ever obtaining long-lived npm credentials.
What the compromised packages contain
The published packages carry two infection vectors:
-
An
optionalDependenciesentry pointing to an orphan commit (github:tanstack/router#79ac49eedf774dd4b0cfa308722bc463cfe5885c) that contains apreparelifecycle hook executingbun run tanstack_runner.js && exit 1. Because npm runs lifecycle scripts for Git-based dependencies during installation, this executes automatically. The&& exit 1makes the failure look like a benign optional dependency resolution problem. -
An embedded
router_init.jspayload — a ~2.3 MB heavily obfuscated file placed directly in the package tarball. The file uses javascript-obfuscator patterns (string-array rotation, hex-encoded identifiers, control-flow flattening) with a secondarybeautify()XOR decode layer containing 148 encoded strings to resist static analysis.
The UiPath namespace compromise used a variant delivery mechanism — a preinstall hook running node setup.mjs that downloads the Bun runtime and executes the payload, matching the pattern from the earlier SAP compromise.
Payload capabilities
Credential harvesting
The payload systematically sweeps every major secrets plane available in modern cloud-native CI environments:
- GitHub Actions: Runner environment variables,
ACTIONS_ID_TOKEN_REQUEST_TOKENfor OIDC, paginated enumeration of repository-level secrets via the GitHub REST API. - AWS: Static credentials, IMDSv2 session-token flow (correctly implementing the
PUT /latest/api/tokenhandshake), ECS task metadata endpoint, Secrets Manager, and SSM Parameter Store across multiple regions. - HashiCorp Vault:
VAULT_TOKEN,VAULT_ADDR, and the Kubernetes-internal endpoint atvault.svc.cluster.local:8200. - Kubernetes: Service account token and CA certificate from
/var/run/secrets/kubernetes.io/serviceaccount/. - Package registries: npm tokens and GitHub PATs.
Self-propagating worm
After harvesting credentials, the payload uses stolen OIDC tokens to mint npm publish tokens, identifies packages the victim has write access to (via npms.io search), bundles a copy of router_init.js into a tarball, and publishes poisoned versions under the latest dist-tag. It also generates valid Sigstore provenance attestations, causing malicious packages to display verified provenance badges.
The payload also targets source repositories directly. Using the GitHub GraphQL createCommitOnBranch mutation with stolen tokens, it commits copies of itself into .github/workflows/, .claude/, and .vscode/ directories, spoofing the commit author as claude@users.noreply.github.com to blend in with legitimate Claude Code activity.
Triple C2 architecture
Exfiltration uses three redundant channels:
- Typosquat domain:
git-tanstack[.]com - Session messenger network: Decentralized, encrypted exfiltration via
*.getsession.orgto a hardcoded recipient ID — significantly harder to disrupt than dedicated domains. - GitHub API dead drops: Creates Dune-themed repositories using stolen tokens, with
Shai-Hulud: Here We Go Againas the repo description.
Persistence and destructive wiper
On developer machines, the malware installs a gh-token-monitor daemon (macOS LaunchAgent or Linux systemd user service) that polls GitHub every 60 seconds. If a monitored token is revoked, the daemon executes rm -rf ~/ to wipe the home directory. Security teams should remove this daemon before revoking GitHub tokens. The daemon auto-exits after 24 hours.
The payload also persists by writing copies of itself into Claude Code’s hook directory (.claude/settings.json) and VS Code’s task runner (.vscode/tasks.json), ensuring re-execution even after npm uninstall.
Python variant
Malicious versions of guardrails-ai@0.10.1 and mistralai@2.4.6 were published to PyPI. These use a different, unobfuscated payload — 13 lines of new code that download and execute git-tanstack[.]com/tmp/transformers.pyz. The Python payload is a modular credential stealer that also targets password vaults (1Password, Bitwarden) and only executes on Linux systems with four or more CPUs.
Like all Mini Shai-Hulud variants, both the JavaScript and Python payloads check for Russian language settings and terminate without exfiltrating data if detected.
Impact
The scale of this compromise makes it one of the largest coordinated supply-chain attacks against the npm ecosystem to date. The combination of trusted publishing abuse, self-propagation, and multi-ecosystem targeting (npm + PyPI) represents a significant evolution in supply-chain attack sophistication.
The worm mechanism means the blast radius extends beyond the initially compromised packages — any maintainer whose CI runner installed an affected version could have had their own packages compromised in turn.
Provenance attestations generated during the attack are cryptographically valid, undermining a key trust signal. As Aikido’s analysis noted: provenance can confirm where a package was built, but it does not prove the build was safe.
Remediation
Check for exposure. Search lockfiles and CI logs for affected package versions. Look for router_init.js, router_runtime.js, tanstack_runner.js, or setup.mjs at package roots. Check for the @tanstack/setup optional dependency marker.
Remove persistence before revoking tokens. Search developer machines for the gh-token-monitor daemon and remove it before rotating GitHub tokens. On macOS: ~/Library/LaunchAgents/com.user.gh-token-monitor.plist. On Linux: ~/.config/systemd/user/gh-token-monitor.service.
Audit IDE directories. Check .claude/ and .vscode/ directories for router_runtime.js or setup.mjs. These persist after npm uninstall.
Rotate all credentials from any system that installed an affected version: npm tokens, GitHub PATs and OIDC trusts, AWS credentials, Vault tokens, Kubernetes service accounts, and CI/CD secrets.
Audit recent publishes. Review npm publishing logs and GitHub Actions runs for unexpected publish activity. A valid provenance record does not guarantee a clean build.
Block C2 infrastructure. Block git-tanstack[.]com and *.getsession.org at the DNS/proxy level. Block outbound traffic to 83.142.209[.]194.
Harden GitHub Actions workflows. Set permissions: id-token: none in all workflows that do not need OIDC publishing. Pin third-party action references to full commit SHAs. Restrict pull_request_target workflows from executing fork code.
Indicators of compromise
File hashes:
router_init.js(2,341,681 bytes) — SHA-256:ab4fcadaec49c03278063dd269ea5eef82d24f2124a8e15d7b90f2fa8601266ctanstack_runner.js— SHA-256:2ec78d556d696e208927cc503d48e4b5eb56b31abc2870c2ed2e98d6be27fc96
Network indicators:
git-tanstack[.]com(C2 domain)seed1.getsession.org,seed2.getsession.org,seed3.getsession.org(Session seed nodes)filev2.getsession.org(Session file server)83.142.209[.]194(Python variant C2)
Persistence artifacts:
~/Library/LaunchAgents/com.user.gh-token-monitor.plist(macOS)~/.config/systemd/user/gh-token-monitor.service(Linux).claude/router_runtime.js,.claude/settings.json,.claude/setup.mjs.vscode/setup.mjs,.vscode/tasks.json
Git dependency marker: github:tanstack/router#79ac49eedf774dd4b0cfa308722bc463cfe5885c