Mini Shai-Hulud: Anatomy of a Massive npm Supply Chain Attack
The npm ecosystem has once again fallen victim to a large-scale supply chain compromise. On May 19, 2026, the npm account atool was compromised, leading to the publication of 637 malicious versions across 317 different packages in a rapid, 22-minute automated burst.
This wasn't a simple credential stealer. The attack deployed a sophisticated toolkit known as "Mini Shai-Hulud," a direct evolution of the framework used in a high-profile SAP compromise just weeks prior. With affected packages like size-sensor and echarts-for-react seeing millions of monthly downloads, the blast radius was immense, targeting everything from AWS infrastructure to the emerging landscape of AI coding agents.
The Infection Vector: Beyond the Preinstall Hook
The attacker utilized two primary execution paths to ensure the payload reached the target system:
- The
preinstallHook: Every compromised version added apreinstallscript (bun run index.js). Because npm resolves semver ranges (e.g.,^3.0.6) to the highest available version, projects performing a clean install automatically pulled the malicious updates regardless of thelatesttag. - GitHub Imposter Commits: In a more insidious move, 630 versions injected an
optionalDependenciesentry pointing to theantvis/G2repository. The attacker used "orphan commits"—commits pushed to a fork that are fetchable by SHA via the parent repository's namespace. By forging the authorship to look like a legitimate maintainer, the attacker hosted a second copy of the payload on GitHub without needing write access to the target repo.
Technical Deep Dive: The Mini Shai-Hulud Toolkit
Credential Harvesting and Cloud Probing
The payload is a 498KB obfuscated Bun script designed for maximum extraction. It employs a scanner architecture that targets over 80 environment variables and uses regex patterns to identify:
- Cloud Keys: AWS (including EC2 IMDSv2 and ECS metadata), GCP service accounts, and Azure credentials.
- Tokens: GitHub PATs, npm tokens, Slack tokens, and HashiCorp Vault tokens.
- Infrastructure: Kubernetes service account tokens and SSH keys.
Container Escape and Privilege Escalation
If the malware detects a Docker socket (e.g., /var/run/docker.sock), it attempts to escape the container. It constructs a privileged Docker container with host bind mounts, effectively granting the attacker full access to the host filesystem.
AI Agent Hijacking
One of the most novel aspects of this attack is the targeting of AI coding tools. The payload injects SessionStart hooks into .claude/settings.json (for Claude Code) and .vscode/tasks.json (for Codex/VS Code).
Whenever a developer starts an AI session or opens a project folder, a Bun bootstrapper is triggered, re-executing the malware. This ensures that even if the initial npm infection is cleaned, the malware persists through the developer's AI-assisted workflow.
C2 Infrastructure: GitHub as a Dead-Drop
Rather than connecting to a suspicious external server, the attacker used the GitHub API as a Command and Control (C2) channel, making the traffic blend in with normal developer activity.
- Exfiltration: Stolen data was committed as Git objects to public repositories created under the compromised token. These repos followed a Dune-themed naming pattern (e.g.,
fremen-sandworm-315) with the description "Shai-Hulud: Here We Go Again." - The
kitty-monitorBackdoor: The payload installs a persistent systemd service or macOS LaunchAgent calledkitty-monitor. This Python daemon polls the GitHub Search API hourly for commits containing the keywordfiredalazer. If it finds a commit with a valid RSA-PSS signature, it downloads and executes arbitrary Python code from the signed URL.
CI/CD Sabotage and Sigstore Abuse
In CI environments, the attack becomes even more dangerous. The payload exchanges GitHub Actions OIDC tokens for npm publish tokens, allowing the attacker to publish new packages using the pipeline's own identity.
Furthermore, the attacker abused Sigstore (Fulcio + Rekor) to sign artifacts. By using stolen OIDC tokens, they created legitimately signed artifacts with forged provenance, potentially tricking downstream security scanners that rely on Sigstore for trust verification.
Community Reaction and Defensive Strategies
The scale of this attack has sparked significant debate within the developer community regarding the inherent insecurity of the npm trust model.
The "Cat and Mouse" Game
Many developers expressed frustration with the reliance on lifecycle scripts (preinstall/postinstall). As one commenter noted:
"Lifecycle scripts should be disabled by default in NPM. It's a convenience feature that provides built-in Arbitrary Code Execution as a feature... and every one of these widespread NPM worm style attacks has propagated through it."
Recommended Mitigations
To protect against similar supply chain attacks, security researchers and community members suggest the following:
- Dependency Cooldowns: Use settings like
npm config set min-release-age=2to avoid installing packages published within the last few days, which is when most malicious bursts are detected and removed. - Hardened Environments: Run development workloads in rootless VMs or DevContainers (e.g., using Podman instead of Docker) to mitigate container escape attempts.
- Lockfile Auditing: Use tools like
pmg(Package Manager Guard) orvetto analyze lockfiles for unexpected size spikes or new lifecycle scripts. - Vendor Dependencies: For critical projects, consider vendoring dependencies—cloning them into your own repo and freezing them—to eliminate the risk of upstream compromises.
Summary of Indicators of Compromise (IoC)
| Category | Indicator |
|---|---|
| Payload SHA256 | a68dd1e6a6e35ec3771e1f94fe796f55dfe65a2b94560516ff4ac189390dfa1c |
| C2 Keyword | firedalazer in GitHub commit messages |
| Persistence | kitty-monitor.service or com.user.kitty-monitor.plist |
| Files | ~/.local/share/kitty/cat.py, .claude/setup.mjs, .vscode/setup.mjs |
| Repo Pattern | {DuneWord1}-{DuneWord2}-{0-999} (e.g., mentat-melange-123) |