system: OPERATIONAL
← back to all hacks
SUPPLY CHAIN CRITICAL

Mini Shai-Hulud: the supply-chain worm that came for the AI tooling stack

Disclosed May 11–18, 2026, the Mini Shai-Hulud worm trojanised 170+ npm and PyPI packages — including Mistral AI, Guardrails AI and TanStack — and persists inside Claude Code and VS Code.

2026-05-22 // 7 min affects: tanstack, mistral-ai-python-sdk, guardrails-ai, uipath-sdk, opensearch-js, claude-code, vs-code, openai-macos-deps

What happened

On May 11, 2026, the threat actor TeamPCP started publishing trojanised versions of widely-used npm packages, starting with TanStack and pivoting within hours to Mistral AI’s Python SDK, Guardrails AI, UiPath, and OpenSearch. Within five hours, more than 400 malicious versions across 172 distinct packages were live on npm and PyPI, with a combined ~518 million historical downloads. On May 12, the fully weaponised source code was briefly published to public GitHub repositories before takedown. A second wave, starting May 18, compromised over 300 @antv packages via a hijacked maintainer account. The campaign — dubbed Mini Shai-Hulud after the larger npm worm of late 2025 — is tracked under CVE-2026-45321 (CVSS 9.6) for the initial TanStack vector.

What makes this one worth a separate write-up on this site: the AI tooling stack was a primary target, not collateral damage.

How it works

The TanStack entry point chains three GitHub Actions misconfigurations. An attacker forks the target repo under a renamed account (zblgg/configuration instead of zblgg/router) to evade fork-list searches, opens a pull request, and triggers a pull_request_target workflow. That workflow checks out and runs code from the attacker’s fork inside the base repo’s trusted context — with secrets, an id-token: write scope, and write access to the GitHub Actions cache. The fork ships a poisoned pnpm store that gets cached; subsequent legitimate CI runs pull the poisoned cache and publish trojanised tarballs to npm with valid provenance attestations, because the publish ran from the official pipeline.

The payload itself is modular:

# Conceptual sketch — taken from public IoC analyses, not runnable.
# The malicious post-install:
#  1. Exfiltrates env to an external C2 (triple-redundant channels).
#  2. Steals: GITHUB_TOKEN, AWS IAM keys, HashiCorp Vault tokens, K8s secrets.
#  3. Forges OIDC-signed npm publish tokens via id-token: write.
#  4. Drops a `gh-token-monitor` service that re-exfiltrates rotated tokens.
#  5. Persists by writing to:
#       ~/.claude/settings.json    # Claude Code hook
#       ~/.vscode/tasks.json       # VS Code task runner
#  6. If api.github.com returns HTTP 40x (token revoked):
#       rm -rf ~/                   # [DESTRUCTIVE — REDACTED PAYLOAD]

The Mistral AI and Guardrails AI PyPI variants ship a different stealer tuned for Python environments — same C2, same exfil targets, but adapted hooks. The @antv wave a week later used a stolen maintainer credential rather than CI poisoning, but the post-install payload is the same family.

Why this matters

Three things make this campaign different from a standard npm supply-chain attack.

The AI stack is in scope. Guardrails AI is, by design, a defensive library that LLM applications wrap around their model calls. A trojanised version flips a guard into an outbound exfiltration channel sitting at the boundary between application and model. Mistral AI’s Python SDK touches every project that calls Mistral’s API. Per Rescana’s analysis, OpenAI’s macOS desktop products were indirectly affected through a TanStack dependency chain.

Persistence targets AI agents specifically. Writing into ~/.claude/settings.json survives npm uninstall and rearms the stealer on the next Claude Code launch. The same pattern works against any agent that auto-loads configuration on start. The agent’s own privilege envelope — shell access, repo access, credential access — becomes the worm’s privilege envelope.

Provenance attestations were valid. Because publishing happened inside the legitimate GitHub Actions pipeline, npm’s provenance signature checks out. Defences that rely on attestation alone do not catch this.

Defenses

There is no model-side mitigation here. The fixes are CI/CD hygiene and credential discipline.

  1. Audit your lockfiles for any version of an affected package installed after April 29, 2026. Use npm audit, Snyk, or the community scanners (GLPMC/Tanstack-Worm-Detector, omarpr/mini-shai-hulud-ioc-scanner). Treat any match as a confirmed incident.
  2. Rotate everything reachable from a compromised workstation or CI runner: GitHub PATs and OAuth tokens, npm tokens, cloud IAM keys (AWS/GCP/Azure), HashiCorp Vault tokens, Kubernetes service-account secrets.
  3. Restrict pull_request_target. Never check out fork code under this trigger without a repository_owner guard. Move fork builds to pull_request with no secrets.
  4. Pin third-party actions by SHA, not tag. Pin internal action consumers too.
  5. Scope id-token: write to the smallest possible job, never at workflow level.
  6. Clean Claude Code and VS Code persistence. Inspect ~/.claude/settings.json and ~/.vscode/tasks.json (and the workspace-local copies) for hook entries you did not author. The IoC scanners flag the known variants.
  7. Re-scope CI caches per branch. TanStack’s hardening included deleting all caches and isolating fork builds on the fork side. Apply the same pattern.

Status

TargetVectorDisclosedPatched / Cleaned
TanStack (@tanstack/*)CI cache poisoning via pull_request_target2026-05-11Malicious versions delisted; CI restructured
Mistral AI Python SDKTrojanised PyPI release2026-05-11Affected versions yanked, new release republished
Guardrails AITrojanised PyPI release2026-05-11Affected versions yanked
UiPath, OpenSearch-jsTrojanised npm releases2026-05-11Delisted
@antv/* (300+ packages)Compromised maintainer account2026-05-18Maintainer reset, packages cleaned
CVE-2026-45321 (TanStack CI chain)2026-05-12CVSS 9.6 — mitigations published

The deeper takeaway: the AI tooling stack is now considered a high-value supply-chain target on its own merits. A worm that lands in ~/.claude/settings.json does not need to break the model — it owns the agent.

Sources