
IOC checker for the TanStack/Mini Shai-Hulud npm supply chain attack (CVE-2026-45321)
curl -fsSL https://raw.githubusercontent.com/nkopylov/tanscript-exploit-check/main/check-tanstack-exploit.sh | bash
Or clone and run locally:
git clone https://github.com/nkopylov/tanscript-exploit-check.git
cd tanscript-exploit-check
./check-tanstack-exploit.sh [project_dir ...]
This checker covers two waves of the Mini Shai-Hulud supply chain attack campaign by TeamPCP:
On May 11, 2026 (19:20-19:26 UTC), an attacker published 84 malicious versions across 42 @tanstack/* npm packages in a 6-minute window. The attack also hit packages from Mistral AI, UiPath, OpenSearch, and others — over 170 packages total across npm and PyPI.
No maintainer credentials were stolen. The attacker exploited the CI/CD trust chain itself via a 3-stage attack:
pull_request_target exploit — A throwaway GitHub fork opened a PR that ran attacker code in the base repo's security contextOn May 19, 2026 (01:39-02:06 UTC), the compromised atool npm account ([email protected]) published 637 malicious versions across 314 packages in two automated waves. High-impact targets include:
size-sensor (4.2M downloads/month)echarts-for-react (3.8M downloads/month)@antv/scale (2.2M downloads/month)timeago.js (1.15M downloads/month)@antv/* and other packagesThis wave used a Bun-based payload (498KB index.js) triggered via "preinstall": "bun run index.js", with a second-stage payload hidden in imposter commits pushed to the antvis/G2 GitHub repo via the fork object sharing exploit.
Both waves use the same "Mini Shai-Hulud" toolkit family:
gh-token-monitor (Wave 1) and kitty-monitor (Wave 2) daemons via LaunchAgent/systemd; hooks in .claude/settings.json and .vscode/tasks.jsontoJSON(secrets), npm OIDC token exchangerm -rf ~/ if GitHub token revoked while daemon activefiredalazer keyword, RSA-PSS signed commandsOnly @tanstack/router* and @tanstack/start* were affected. NOT affected: @tanstack/query*, @tanstack/table*, @tanstack/form*, @tanstack/virtual*, @tanstack/store.
See the full advisory for all 42 packages.
All packages published by npm user atool ([email protected]) received malicious versions on May 19, 2026. High-impact packages include:
| Package | Monthly downloads |
|---|---|
size-sensor | 4.2M |
echarts-for-react | 3.8M |
@antv/scale | 2.2M |
Plus 310+ packages primarily in the @antv/* scope (@antv/g2, @antv/g6, @antv/l7, @antv/s2, @antv/x6, @antv/f2, etc.), ai-figure, timeago-react, jest-canvas-mock, jest-date-mock, and others.
See the SafeDep writeup for the full list.
CRITICAL: Disable the dead-man's switch BEFORE revoking any tokens. The malware wipes
$HOMEif tokens are revoked while the daemon is active.
gh-token-monitor AND kitty-monitor) and remove LaunchAgent/systemd services.claude/router_runtime.js, .vscode/setup.mjs, ~/.local/share/kitty/cat.py, /var/tmp/.gh_update_state, etc.)node_modules and lockfiles, reinstall with --ignore-scriptsapi.masscan.cloud, filev2.getsession.org, git-tanstack.com, t.m-kosche.com)Copy-paste these prompts into your AI coding agent (Claude Code, Cursor, Cline/OpenClaw, Windsurf, Hermes, etc.) to harden your project against supply chain attacks like this one.
Harden this project's npm configuration against supply chain attacks. Do the following:
Pin exact versions: Remove all
^and~prefixes from every dependency inpackage.jsonso thatnpm installnever silently pulls a newly published version.Disable postinstall scripts by default: Add
ignore-scripts=trueto.npmrc. Then add an explicit"preinstall"script inpackage.jsonthat runs only the known-safe lifecycle scripts this project actually needs (if any).Enforce lockfile-only installs in CI: Make sure CI uses
npm ci(notnpm install). If there's a CI config file, verify this. If not, note it as a manual step.Add provenance verification: Add
npm audit signaturesas a step in the CI pipeline and as a pre-push git hook.
Audit and harden this project's AI coding agent configuration against supply chain injection attacks (like the TanStack/Mini Shai-Hulud campaign that injected malicious hooks into
.claude/settings.jsonand.vscode/tasks.json). Do the following:Claude Code (
.claude/):
- Review
.claude/settings.jsonand.claude/settings.local.jsonfor anyhooksentries that run shell commands. Flag anything that executes.js,.mjs, or.shfiles — especially from within.claude/,.vscode/, ornode_modules/.- Remove any hooks you cannot trace to a legitimate, user-created purpose.
- Add a
.gitignorerule to prevent from being committed (it should stay local).
Audit and harden the GitHub Actions configuration in this repository against CI/CD supply chain attacks. Do the following:
Remove or refactor any
pull_request_targettriggers — these run workflow code in the context of the base repo with access to secrets, even when triggered by a fork. Replace withpull_request+ a separate approval-gated workflow if needed.Pin all third-party actions to full commit SHAs (not tags or branches). For example, replace
actions/checkout@v4withactions/checkout@<full-sha>. Add a comment with the tag for readability.Apply least-privilege permissions to every workflow. Add explicit
permissions:blocks. Most workflows only needcontents: read. Publish workflows needid-token: write— and nothing else.Restrict cache scope: If using
actions/cache, ensure cache keys are branch-scoped to prevent cross-branch poisoning. Addrestore-keyscarefully — don't restore caches from untrusted branches.Add StepSecurity Harden-Runner as the first step in every job: with (or if you know your allowed endpoints).
MIT
| # | Check | Description |
|---|
| 1 | Dead-man's switch | Persistence daemons: gh-token-monitor (Wave 1), kitty-monitor (Wave 2) |
| 2 | Malicious processes | Known attacker process names (both waves) |
| 3 | Payload files | Known malicious files by name and SHA-256 hash (4 hashes) |
| 4 | Claude Code hooks | Injected hooks in .claude/settings.json + generic SessionStart heuristic |
| 5 | VS Code tasks | Injected tasks in .vscode/tasks.json + generic runOn: folderOpen heuristic |
| 6 | GitHub Actions | toJSON(secrets) in any workflow + pull_request_target warning |
| 7 | npm lockfiles | Compromised package versions in lockfiles |
| 8 | optionalDependencies | Malicious @tanstack/setup and @antv/setup + 4 imposter commit SHAs |
| 9 | Network connections | Active connections to C2 infrastructure (5 domains/IPs) |
| 10 | DNS cache | Prior resolution of attacker domains (4 domains) |
| 11 | Git branches | Dune-themed attacker branch naming pattern |
| 12 | Lifecycle scripts | Heuristic: bun run in preinstall/postinstall (installed packages) |
| 13 | Dead-drop C2 | firedalazer keyword and "Shai-Hulud" markers in git history |
| 14 | GitHub deps | Heuristic: github: dependencies pinned to commit SHA in optionalDeps |
| Package | Malicious versions | First safe version |
|---|
@tanstack/react-router | 1.169.5, 1.169.8 | 1.169.9 |
@tanstack/router-core | 1.169.5, 1.169.8 | 1.169.9 |
@tanstack/vue-router | 1.169.5, 1.169.8 | 1.169.9 |
@tanstack/solid-router | 1.169.5, 1.169.8 | 1.169.9 |
@tanstack/react-start | 1.167.68, 1.167.71 | 1.167.72 |
@tanstack/router-plugin | 1.167.38, 1.167.41 | 1.167.42 |
timeago.js |
| 1.15M |
Create an update policy script: Create a scripts/safe-update.sh that:
npm view <pkg> time --json)npm install <pkg>@latest --save-exactDo not change any application code. Only touch config files, .npmrc, package.json scripts, and CI config.
.claude/settings.local.jsonVS Code (.vscode/):
.vscode/tasks.json and .vscode/launch.json for tasks that execute unexpected scripts or binaries.setup.mjs, router_runtime.js, or other names that don't belong to this project..vscode/extensions.json for extensions you don't recognize.Cursor (.cursor/):
.cursor/settings.json and any rules files for injected commands or hooks..vscode/ config.General:
preinstall, postinstall, prepare, or prestart scripts in package.json that you didn't write. Flag suspicious ones..github/workflows/ for any workflow using pull_request_target — flag it as a security risk with a comment explaining why..gitignore excludes agent session files that could leak credentials (.claude/projects/, .cursor/logs/, etc.).Report what you found and what you changed. Do not modify application code.
step-security/harden-runner@v2egress-policy: auditblockCheck for secrets in workflow logs: Ensure no workflow step prints ${{ secrets.* }} or ${{ toJSON(secrets) }} to stdout.
Report all changes. Do not modify application code.