A supply chain attack campaign named Mini Shai-Hulud was recently reported. It is a worm-type malware that has compromised multiple npm packages, starting with the @tanstack/* ecosystem, publishing malicious versions through the projects’ own GitHub Actions pipelines using stolen OIDC tokens. These malicious versions include obfuscated payloads that steal CI/CD, cloud, repository, and development tool credentials, persist on developer machines, and self-propagate to other packages using valid npm and OIDC tokens.
npm is the main package registry for the JavaScript and TypeScript ecosystem and has become a critical piece of the modern software supply chain. Millions of applications and CI/CD pipelines automatically consume new versions of packages published on npm, which makes any compromise of maintainer accounts or their pipelines a vector of massive and cross-cutting impact. This structural dependency makes it essential to apply additional security controls (intermediate registries, version validation, publishing policies, and observability) instead of blindly trusting that everything coming from npm is reliable.
The severity is critical because the worm can read memory from the GitHub Actions runner and extract absolutely all secrets from a pipeline (including “masked” secrets), abuse OIDC to publish new malicious packages with valid SLSA Build Level 3, and also establishes persistence on developer workstations and IDEs. In practice, any environment that has installed compromised versions must assume total compromise of all secrets accessible from that machine or pipeline.
Compromised packages
| Package | Ecosystem | Compromised versions | Quick comment |
| @tanstack/react-router | npm | 1.169.5, 1.169.8 | Very widely used React router in front-end. |
| @tanstack/solid-router | npm | 1.169.5, 1.169.8 | Router for SolidJS applications. |
| @tanstack/vue-router | npm | 1.169.5, 1.169.8 | Router for Vue applications. |
| @tanstack/react-start | npm | 1.167.68, 1.167.71 | Full-stack framework on React. |
| @tanstack/solid-start | npm | 1.167.65, 1.167.68 | Full-stack framework on Solid. |
| @tanstack/vue-start | npm | 1.167.61, 1.167.64 | Full-stack framework on Vue. |
| @mistralai/mistralai | npm | 2.2.3, 2.2.4 | Official Mistral AI TypeScript SDK. |
| mistralai | PyPI | 2.4.6 | Mistral AI Python SDK. |
| guardrails-ai | PyPI | 0.10.1 | Popular library for LLM guardrails. |
| @opensearch-project/opensearch | npm | 3.6.2 | OpenSearch client. |
| @uipath/cli | npm | 1.0.1 | UiPath CLI used in automation/RPA. |
| @uipath/robot | npm | 1.3.4 | UiPath robot component. |
| @uipath/project-packager | npm | 1.1.16 | Key package in UiPath projects. |
| @draftlab/auth | npm | 0.24.1, 0.24.2 | Auth for DraftLab applications. |
| @squawk/airport-data | npm | 0.7.4, 0.7.5, 0.7.7 | Aeronautical data, used in aviation solutions. |
Technical details of the attack
Compromise vector and package publishing
The TeamPCP threat group launched a new wave of the Mini Shai-Hulud worm targeting TanStack and other high-value projects. To do this, the attacker created a malicious fork of the TanStack/router repository using the voicproducoes account and added two key artifacts: a fake @tanstack/setup package and a large, heavily obfuscated file called tanstack_runner.js. This @tanstack/setup package is declared as an optional dependency via a github: URL with a commit hash that appears to belong to the official TanStack repository, but actually points to that malicious fork.
Two characteristic changes were observed in each compromised package:
- An optionalDependencies block was added in package.json pointing to @tanstack/setup via a github:tanstack/router# URL.
- A large router_init.js file was added at the root of the package, which should not be present according to the files field (which only lists dist and src), evidencing tarball manipulation outside the normal build process.
The malicious packages were not published with credentials stolen from a maintainer, but by compromising the GitHub Actions pipeline itself, leveraging the runner’s OIDC token to publish directly to npm. As part of the campaign, the malicious packages include SLSA v1 level 3 attestations issued by npm’s signing infrastructure (Sigstore/Fulcio/Rekor), so that they appear legitimate despite having been generated by an already compromised pipeline.
Characteristics of the worm and payload
The file router_init.js:
(SHA-256 hash ab4fcadaec49c03278063dd269ea5eef82d24f2124a8e15d7b90f2fa8601266c) contains a payload of about 2.3 MB highly obfuscated that implements a multi-stage stealer with worm capabilities.
Main capabilities:
- Advanced obfuscation: three layers of obfuscation, including obfuscated string table, a substitution cipher with PBKDF2-SHA256 and secondary payloads encrypted with AES-256-GCM that are decompressed with Bun.gunzipSync.
- Use of the Bun runtime: the payload installs and uses Bun 1.3.13 instead of Node.js, evading security tools focused on Node hooks (–require, etc.).
- GitHub Actions runner memory scraping: a Python payload reads /proc/<pid>/mem of the Runner.Worker process to extract internal JSON of the type {“value”:”…”,”isSecret”:true}, obtaining all secrets configured in the workflow, even if they are masked or never written to disk.
- Cloud credential theft: invokes AWS metadata (169.254.169.254 IMDSv2), ECS (169.254.170.2), and local HashiCorp Vault sockets (127.0.0.1:8200) to obtain temporary credentials, tokens, and secrets.
- Credential file collection: traverses more than 100 known paths to extract npm tokens, GitHub PATs, AWS, GCP, Azure keys, cryptocurrency wallet files, shell histories, and AI and messaging tool configurations.
- Workstation persistence: creates files in .claude/ and .vscode/ to hook into session opening events in Claude Code and folder opening events in VS Code, and also installs gh-token-monitor type services via LaunchAgents on macOS or systemd user on Linux.
Self-propagation
Mini Shai-Hulud behaves as an npm worm, using stolen tokens to compromise more packages:
- Searches for npm tokens with bypass_2fa true (publishing without 2FA) and, if found, enumerates all packages of the same maintainer using the npm search API.
- In CI/CD environments, exchanges GitHub OIDC tokens for per-package npm publishing tokens via the oidc/token/exchange endpoint, bypassing the standard authentication flow.
- Publishes infected artifacts for all packages with sufficient permissions, replicating the infection.
It also injects malicious workflows into .github/workflows/codeql_analysis.yml that use toJSON(secrets) to serialize all repository secrets and exfiltrate them directly to C2 infrastructure (via HTTP POST) or as artifacts uploaded in seemingly legitimate actions.
Exfiltration and ransom
Data exfiltration combines two main channels:
- Session Protocol CDN: uploading encrypted blobs to filev2.getsession.org, part of the Session network infrastructure, using hybrid RSA-4096 + AES-256-GCM encryption.
- Dead-drop on GitHub: uses the GitHub GraphQL API to create commits in attacker-controlled repositories, signed as claude@users.noreply.github.com, with messages chore: update dependencies and branches that mimic the Dependabot convention (dependabot/github_actions/format/<dune-word>).
The campaign also includes an extortion/ransom component: the malware creates npm tokens with the description IfYouRevokeThisTokenItWillWipeTheComputerOfTheOwner, indicating that revoking the token may trigger a destructive routine that wipes the owner’s computer.
Detection and key indicators
At the sysadmin/DevOps level, these are very practical indicators:
- Presence of router_init.js in node_modules or at the root of @tanstack/* packages.
- package.json with optionalDependencies pointing to github:tanstack/router#79ac49eedf774dd4b0cfa308722bc463cfe5885c.
- Unusual tarball size growth (packages ~900 KB versus ~190 KB for clean versions).
- bun processes running during npm install of packages that don’t normally use Bun.
- python3 processes reading /proc/*/mem on CI runners.
- Network connections from npm install or CI jobs to filev2.getsession.org, api.masscan.cloud, or git-tanstack.com.
- npm tokens with the description IfYouRevokeThisTokenItWillWipeTheComputerOfTheOwner.
- Unexpected commits signed as claude@users.noreply.github.com with branches like dependabot/github_actions/format/.
| Type | Value | Description / suggested use |
| SHA-256 | ab4fcadaec49c03278063dd269ea5eef82d24f2124a8e15d7b90f2fa8601266c | Hash of router_init.js (main payload in TanStack packages). Block/alert in EDR, AV, and forensic searches. |
| Process | bun | bun executions during npm install or CI jobs in repositories that don’t legitimately use Bun. Create detection/anomaly rule. |
| Local file | router_init.js | Malicious file added at the root of @tanstack/* packages. Search on disk, containers, and artifacts. |
| Local file | .claude/router_runtime.js, .claude/setup.mjs | Persistence in development environments (Claude Code). Review and remove. |
| Local file | .vscode/setup.mjs | Persistence in VS Code via tasks/extensions. Review and remove. |
| Service | gh-token-monitor | User service (systemd / LaunchAgent) used to monitor tokens. Search and disable. |
| C2 Domain | filev2.getsession.org | Session network CDN used to exfiltrate encrypted data. Block or alert in proxy/firewall. |
| Domain/C2 | api.masscan.cloud | Related infrastructure used in previous campaigns of the same family. Monitor outbound connections. |
| GitHub Infra | Commits with author claude@users.noreply.github.com on branches dependabot/github_actions/format/ | Dead-drop on victim repos. Create hunting query in GitHub / source code. |
| Internal network | http://169.254.169.254 | Access to AWS IMDS (credential theft). Alert if seen from containers/runners that shouldn’t touch metadata. |
| Internal network | http://169.254.170.2 | ECS/Task metadata endpoint. Same as above. |
| Internal network | http://127.0.0.1:8200 | Attempts to access local HashiCorp Vault. Monitor on CI runners. |
| npm Token | Description IfYouRevokeThisTokenItWillWipeTheComputerOfTheOwner | Malicious tokens created by the malware. Enumerate npm tokens and treat them as a critical compromise indicator. |
Recommended mitigation
Assess exposure (dev and CI/CD)
- Review package-lock.json / pnpm-lock.yaml / yarn.lock for affected versions of @tanstack/* and other listed packages (UiPath, DraftLab, etc.).
- Search for router_init.js and references to @tanstack/setup in node_modules and local repos.
- Review GitHub Actions (or CI) logs for:
- mentions of @tanstack/setup,
- use of bun run tanstack_runner.js or other Bun commands,
- unusual network connections during dependency installation.
Containment and forensic analysis
- Isolate any dev machine or CI runner that has run npm install with compromised versions of @tanstack/*.
- Obtain a forensic image before cleaning, especially if there are npm tokens with the threat message.
- Do not immediately revoke npm tokens with IfYouRevokeThisTokenItWillWipeTheComputerOfTheOwner until you isolate and analyze the machine.
Persistence removal:
On developer machines
- Claude / VS Code:
- Delete .claude/router_runtime.js and .claude/setup.mjs.
- Review/restore .claude/settings.json.
- Review .vscode/tasks.json and delete .vscode/setup.mjs.
- Token monitoring services:
- macOS: launchctl unload ~/Library/LaunchAgents/com.user.gh-token-monitor.plist and delete plist, ~/.local/bin/gh-token-monitor.sh, and ~/.config/gh-token-monitor.
- Linux: systemctl –user stop/disable gh-token-monitor and delete ~/.config/systemd/user/gh-token-monitor.service.
In repositories
- Audit .github/workflows/codeql_analysis.yml and recent workflows that use toJSON(secrets) or POST to unexpected domains, and remove or correct them.
Secret rotation
Assuming compromise where a malicious version was installed:
- Rotate:
- npm tokens (including CI ones),
- PATs and GitHub tokens (user and Actions),
- AWS, GCP, Azure, Vault and other cloud credentials,
- SSH keys used on those machines or runners.
- If there are cryptocurrency wallets, move funds to new wallets.
Solution and hardening recommendations
Dependency update / cleanup
- Pin to safe versions of @tanstack/* according to the clean version tables published by TanStack and StepSecurity/Snyk reports; the operational rule is to revert to the last version prior to the attack dates (May 11, 2026 for TanStack) and remove versions published in that window.
- After adjusting versions, run:
- rm -rf node_modules
- npm install (or pnpm/yarn equivalent) to regenerate the tree with non-compromised versions.
Harden CI/CD pipelines
Specific best practices for this case:
- Restrict OIDC token permissions in workflows to the minimum (id-token: write only where essential) and use separate jobs with least privilege policies for publishing to registries.
- Implement a cooldown for new package versions (e.g., 3-10 days) in an intermediate registry (Artifactory, Nexus, or solutions like StepSecurity Secure Registry), so that packages published minutes ago are not automatically consumed in CI.
- Implement egress control on CI runners (whitelists of allowed domains), similar to solutions like Harden-Runner, which block connections to C2 domains in real time.
- Automate package reputation and signature checks (SLSA, Sigstore) understanding that provenance does not guarantee absence of compromise, only identifies the pipeline that generated the artifact.
Additional development controls
- Introduce continuous dependency scanning for malware and known campaigns (Mini Shai-Hulud, Shai-Hulud 2.0, etc.) using updated intelligence feeds from security providers.
- Limit the use of github: dependencies in package.json, especially when pointing to fork commits, and establish manual reviews in PRs that introduce this pattern.
References
- StepSecurity – “TeamPCP’s Mini Shai-Hulud Is Back: A Self-Spreading Supply Chain Attack Compromises TanStack npm Packages”
- TanStack – “Postmortem: TanStack npm supply-chain compromise”
- GitHub Security Advisory – GHSA-g7cv-rxg3-hmpx: Malware in @tanstack/* packages exfiltrates cloud credentials
- Snyk – “TanStack npm Packages Hit by Mini Shai-Hulud”
- Wiz – “Mini Shai-Hulud Strikes Again: TanStack + more npm packages compromised”
- Saiyam Pathak – “How an Attacker Hijacked 84 TanStack Packages”
- GitHub – TanStack/router issue #7383 “Several npm latest releases are compromised”
- GitLab / GitHub Advisory Mirror – CVE-2026-45321: Malware in @tanstack/* packages
Annex – All compromised packages
In total, the Mini Shai-Hulud campaign affected 170 different packages and resulted in at least 348 malicious artifacts published in the npm and PyPI registries during this wave.
| # | Scope / package | Compromised versions | Ecosystem | Relevant notes |
| 1 | @mistralai/mistralai | 2.2.3, 2.2.4 | npm | Official Mistral AI TypeScript SDK |
| 2 | @mistralai/mistralai-azure | 1.7.2, 1.7.3 | npm | Client for Azure deployments |
| 3 | @mistralai/mistralai-gcp | 1.7.2, 1.7.3 | npm | Client for GCP deployments |
| 4 | mistralai | 2.4.6 | PyPI | Mistral Python SDK; treat environments as compromised |
| 5 | guardrails-ai | 0.10.1 | PyPI | LLM guardrails library affected in the campaign |
| 6 | @opensearch-project/opensearch | 3.6.2 | npm | OpenSearch-related client |
| 7 | @tanstack/router-utils | 1.161.11, 1.161.14 | npm | Part of the TanStack routing stack |
| 8 | @tanstack/router-core | 1.169.5, 1.169.8 | npm | TanStack router core |
| 9 | @tanstack/history | 1.161.9, 1.161.12 | npm | History management for the router |
| 10 | @tanstack/react-router | 1.169.5, 1.169.8 | npm | React router, widely used in front-end |
| 11 | @tanstack/react-router-devtools | 1.166.16, 1.166.19 | npm | React router devtools |
| 12 | @tanstack/react-router-ssr-query | 1.166.15, 1.166.18 | npm | SSR + queries integration |
| 13 | @tanstack/react-start | 1.167.68, 1.167.71 | npm | react-start full-stack framework |
| 14 | @tanstack/react-start-client | 1.166.51, 1.166.54 | npm | react-start client |
| 15 | @tanstack/react-start-rsc | 0.0.47, 0.0.50 | npm | React Server Components support |
| 16 | @tanstack/react-start-server | 1.166.55, 1.166.58 | npm | react-start server |
| 17 | @tanstack/router-cli | 1.166.46, 1.166.49 | npm | TanStack router CLI |
| 18 | @tanstack/router-devtools | 1.166.16, 1.166.19 | npm | Router (core) devtools |
| 19 | @tanstack/router-devtools-core | 1.167.6, 1.167.9 | npm | Router devtools core |
| 20 | @tanstack/router-generator | 1.166.45, 1.166.48 | npm | Route generator |
| 21 | @tanstack/router-plugin | 1.167.38, 1.167.41 | npm | Router plugin |
| 22 | @tanstack/router-ssr-query-core | 1.168.3, 1.168.6 | npm | Core for SSR + queries |
| 23 | @tanstack/router-vite-plugin | 1.166.53, 1.166.56 | npm | Vite plugin for router |
| 24 | @tanstack/solid-router | 1.169.5, 1.169.8 | npm | Router for SolidJS |
| 25 | @tanstack/solid-router-devtools | 1.166.16, 1.166.19 | npm | Devtools for Solid router |
| 26 | @tanstack/solid-router-ssr-query | 1.166.15, 1.166.18 | npm | SSR + queries for Solid |
| 27 | @tanstack/solid-start | 1.167.65, 1.167.68 | npm | Solid Start framework |
| 28 | @tanstack/solid-start-client | 1.166.50, 1.166.53 | npm | Solid Start client |
| 29 | @tanstack/solid-start-server | 1.166.54, 1.166.57 | npm | Solid Start server |
| 30 | @tanstack/start-client-core | 1.168.5, 1.168.8 | npm | TanStack Start client core |
| 31 | @tanstack/start-fn-stubs | 1.161.9, 1.161.12 | npm | Start function stubs |
| 32 | @tanstack/start-plugin-core | 1.169.23, 1.169.26 | npm | Start plugins core |
| 33 | @tanstack/start-server-core | 1.167.33, 1.167.36 | npm | Start server core |
| 34 | @tanstack/start-static-server-functions | 1.166.44, 1.166.47 | npm | Static server functions |
| 35 | @tanstack/start-storage-context | 1.166.38, 1.166.41 | npm | Start storage context |
| 36 | @tanstack/arktype-adapter | 1.166.12, 1.166.15 | npm | ArkType types adapter |
| 37 | @tanstack/valibot-adapter | 1.166.12, 1.166.15 | npm | Valibot adapter |
| 38 | @tanstack/virtual-file-routes | 1.161.10, 1.161.13 | npm | Virtual file-based routes |
| 39 | @tanstack/vue-router | 1.169.5, 1.169.8 | npm | Router for Vue |
| 40 | @tanstack/vue-router-devtools | 1.166.16, 1.166.19 | npm | Vue router devtools |
| 41 | @tanstack/vue-router-ssr-query | 1.166.15, 1.166.18 | npm | SSR + queries for Vue |
| 42 | @tanstack/vue-start | 1.167.61, 1.167.64 | npm | Vue Start framework |
| 43 | @tanstack/vue-start-client | 1.166.46, 1.166.49 | npm | Vue Start client |
| 44 | @tanstack/vue-start-server | 1.166.50, 1.166.53 | npm | Vue Start server |
| 45 | @tanstack/zod-adapter | 1.166.12, 1.166.15 | npm | Zod adapter |
| 46 | @tanstack/eslint-plugin-router | 1.161.9, 1.161.12 | npm | ESLint plugin for router |
| 47 | @tanstack/eslint-plugin-start | 0.0.4, 0.0.7 | npm | ESLint plugin for Start |
| 48 | @squawk/airways | 0.4.2, 0.4.3, 0.4.5 | npm | Part of the Squawk aeronautical ecosystem |
| 49 | @squawk/airport-data | 0.7.4, 0.7.5, 0.7.7 | npm | Airport data |
| 50 | @squawk/airports | 0.6.2, 0.6.3, 0.6.5 | npm | Airports API |
| 51 | @squawk/airspace | 0.8.1, 0.8.2, 0.8.4 | npm | Airspace data |
| 52 | @squawk/airspace-data | 0.5.3, 0.5.4, 0.5.6 | npm | Airspace dataset |
| 53 | @squawk/airway-data | 0.5.4, 0.5.5, 0.5.7 | npm | Airways dataset |
| 54 | @squawk/fix-data | 0.6.4, 0.6.5, 0.6.7 | npm | Navigation fixes (points) |
| 55 | @squawk/fixes | 0.3.2, 0.3.3, 0.3.5 | npm | Fixes API |
| 56 | @squawk/flight-math | 0.5.4, 0.5.5, 0.5.7 | npm | Flight calculation utilities |
| 57 | @squawk/flightplan | 0.5.2, 0.5.3, 0.5.5 | npm | Flight planning |
| 58 | @squawk/geo | 0.4.4, 0.4.5, 0.4.7 | npm | Geospatial functions |
| 59 | @squawk/icao-registry | 0.5.2, 0.5.3, 0.5.5 | npm | ICAO registry |
| 60 | @squawk/icao-registry-data | 0.8.4, 0.8.5, 0.8.7 | npm | ICAO dataset |
| 61 | @squawk/mcp | 0.9.1, 0.9.2, 0.9.4 | npm | MCP tools |
| 62 | @squawk/navaid-data | 0.6.4, 0.6.5, 0.6.7 | npm | Radio-aid data |
| 63 | @squawk/navaids | 0.4.2, 0.4.3, 0.4.5 | npm | Navaids API |
| 64 | @squawk/notams | 0.3.6, 0.3.7, 0.3.9 | npm | NOTAMs |
| 65 | @squawk/procedure-data | 0.7.3, 0.7.4, 0.7.6 | npm | Procedure data |
| 66 | @squawk/procedures | 0.5.2, 0.5.3, 0.5.5 | npm | Procedures API |
| 67 | @squawk/types | 0.8.1, 0.8.2, 0.8.4 | npm | Shared types |
| 68 | @squawk/units | 0.4.3, 0.4.4, 0.4.6 | npm | Units of measurement |
| 69 | @squawk/weather | 0.5.6, 0.5.7, 0.5.9 | npm | Weather data |
| 70 | @uipath/* (multiple packages) | Versions listed in your original text | npm | More than 50 packages in the @uipath scope affected |
| 71 | @draftauth/client | 0.2.1, 0.2.2 | npm | DraftAuth authentication client |
| 72 | @draftauth/core | 0.13.1, 0.13.2 | npm | DraftAuth core |
| 73 | @draftlab/auth | 0.24.1, 0.24.2 | npm | DraftLab auth |
| 74 | @draftlab/auth-router | 0.5.1, 0.5.2 | npm | Authentication router |
| 75 | @draftlab/db | 0.16.1, 0.16.2 | npm | DraftLab data layer |
| 76 | @beproduct/nestjs-auth | 0.1.2–0.1.17, 0.1.19 | npm | Multiple compromised versions |
| 77 | @dirigible-ai/sdk | 0.6.2, 0.6.3 | npm | Dirigible AI SDK |
| 78 | @ml-toolkit-ts/preprocessing | 1.0.2, 1.0.3 | npm | Part of ml-toolkit-ts |
| 79 | @ml-toolkit-ts/xgboost | 1.0.3, 1.0.4 | npm | XGBoost binding |
| 80 | ml-toolkit-ts | 1.0.4, 1.0.5 | npm | Main package |
| 81 | @tallyui/* (components, core, etc.) | Versions indicated in your list | npm | @tallyui scope affected as a block |
| 82 | @mesadev/rest | 0.28.3 | npm | Part of the MesaDev ecosystem |
| 83 | @mesadev/saguaro | 0.4.22 | npm | MesaDev package |
| 84 | @mesadev/sdk | 0.28.3 | npm | MesaDev SDK |
| 85 | safe-action | 0.8.3, 0.8.4 | npm | Safe actions package |
| 86 | @supersurkhet/cli | 0.0.2–0.0.7 | npm | Supersurkhet CLI |
| 87 | @supersurkhet/sdk | 0.0.2–0.0.7 | npm | Supersurkhet SDK |
| 88 | cmux-agent-mcp | 0.1.3–0.1.8 | npm | MCP tool |
| 89 | git-git-git | 1.0.8–1.0.10, 1.0.12 | npm | git utilities |
| 90 | git-branch-selector | 1.3.3–1.3.5, 1.3.7 | npm | git branch selector |
| 91 | nextmove-mcp | 0.1.3–0.1.5, 0.1.7 | npm | MCP client |
| 92 | agentwork-cli | 0.1.4, 0.1.5 | npm | AgentWork CLI |
| 93 | wot-api | 0.8.1, 0.8.2, 0.8.4 | npm | WoT API |
| 94 | cross-stitch | 1.1.3, 1.1.4, 1.1.6 | npm | Utility library |
| 95 | ts-dna | 3.0.1, 3.0.2, 3.0.4 | npm | TypeScript package |