Shai-Hulud strikes again: CHAINDROP worm hits 400+ npm packages
Elastic Security Labs identified a new Shai-Hulud campaign deploying a self-propagating worm called CHAINDROP that has compromised over 400 npm packages by targeting the maintainer of the widely-used keyv library. The worm abuses npm preinstall hooks to execute a cross-platform dropper (setup.mjs) that downloads the bun runtime and executes an obfuscated credential harvester targeting AI tooling, cloud providers, and developer credentials. C2 resolution uses an Ethereum smart contract for infrastructure agility, with fallbacks via GitHub commit history and victim GitHub repositories. Stolen npm tokens with write access and 2FA bypass trigger worm propagation to all packages the victim can publish.
- domainawqhnjewqjkl[.]icuDead-drop C2 domain observed during detonation, retrieved at runtime via Ethereum smart contract query
- domainnpm-cache[.]comDead-drop C2 domain observed during detonation, retrieved at runtime via Ethereum smart contract query
- sha25654dc7ea54a1317cca0e890a2770630cf7fa6c97813e0cb9d2caa93012b350668SHA-256 of a second variant of setup.mjs dropper used in the campaign
- sha2569fc2570b7cef51c1b8df116d144d11ff4096357be7d2c4c6367cfc2509cf1bccSHA-256 of Math_Symbol.js, the main obfuscated credential-harvesting payload delivered by the CHAINDROP dropper in directly compromised keyv monorepo packages
- sha256fd3ca4007b225fdf8de7af4345a19179d5efa8c4bb9205f88cda806e5684b1ebSHA-256 of setup.mjs, the dropper script injected into package.json preinstall hooks that downloads bun runtime and executes the payload
Detection / Hunteropenrouter
What Happened
A malicious worm called CHAINDROP has infected over 400 popular npm software packages by first compromising a widely used library called keyv, which receives over 600 million monthly downloads. The worm automatically spreads by stealing a developer's npm login credentials and using them to inject malicious code into every other package that developer maintains. When someone installs an infected package, the malware runs automatically, steals passwords and access keys for cloud services, AI tools, and developer platforms, and sends them to the attackers. The attackers use a blockchain-based system (Ethereum) to hide and rotate their command servers, making them harder to block. Developers and organizations using npm packages should avoid immediate adoption of new package versions, revoke all credentials on affected machines, enable two-factor authentication on npm accounts, and upgrade to npm 12 or later which blocks the malicious installation hooks by default.
Key Takeaways
- Over 400 npm packages compromised via CHAINDROP, a self-propagating worm that abuses stolen npm credentials to backdoor every package a maintainer can publish without 2FA
- Payload is cross-platform (Linux, macOS, Windows), triggered via npm preinstall hooks, and uses the bun JavaScript runtime to execute a heavily obfuscated 711KB credential harvester
- C2 infrastructure is resolved dynamically via an Ethereum smart contract, allowing the attacker to rotate endpoints without updating the payload; fallbacks include GitHub commit history and exfiltration via victim's own GitHub repos
- Credential harvester targets 300+ patterns including AI tooling (Anthropic, Claude, OpenAI, Cursor, Gemini), cloud providers (AWS, GCP, Azure, Alibaba), GitHub tokens, SSH keys, Kubernetes tokens, and npm tokens
- Worm also injects malicious hooks into .claude/settings.json and .vscode/tasks.json, meaning developers can be infected simply by opening an infected repository in VS Code or starting a Claude session
Affected Systems
- npm ecosystem packages (keyv, flat-cache, cacheable-request, cacheable, cache-manager and 400+ others)
- Linux, macOS, and Windows developer machines running Node.js
- VS Code and Claude Code users opening infected repositories
- GitHub repositories with accessible branches via stolen GitHub App tokens
Vulnerabilities (CVEs)
None identified.
Attack Chain
- Initial Access: Trojanized npm packages with preinstall hooks in package.json execute malicious code upon installation
- Execution: setup.mjs dropper downloads bun v1.3.13 runtime from official GitHub releases, extracts it, and uses it to execute the obfuscated payload (Math_Symbol.js or math_init.js)
- Credential Harvesting: Payload scans 300+ patterns across developer machine for AI tooling, cloud provider, GitHub, SSH, Kubernetes, and npm credentials
- C2 Resolution: Malware queries Ethereum smart contract at 0xE1f2395ee43e45A1556EC6438a88c31B83493103 via multiple RPC providers to retrieve current exfiltration endpoint; falls back to GitHub commit history signed marker, then to victim's own GitHub repo
- Exfiltration: Stolen credentials gzip-compressed, AES-256-GCM encrypted with random key, RSA-wrapped with attacker's public key, and sent to resolved C2 endpoint
- Propagation: If stolen npm token has write permissions and 2FA bypass, worm enumerates all publishable packages, injects malicious components, patches package.json, and republishes to npm registry
Detection Availability
- YARA Rules: No
- Sigma Rules: No
- Snort/Suricata Rules: No
- KQL Queries: No
- Splunk SPL Queries: No
- EQL Queries: No
- Other Detection Logic: Yes
- Platforms: Elastic Defend detection rules, Elastic protections artifacts, Elastic EQL hunting queries
Elastic provides multiple detection rules covering dropper execution, bun runtime download, DNS requests to crypto/DHT services, and script deletion. Three EQL hunting queries are included for process execution of setup.mjs, bun executing payload files, and DNS lookups to known C2 and RPC provider domains. Rules are available in the Elastic detection-rules and protections-artifacts GitHub repositories.
Detection Engineering Assessment
| Dimension | Rating | Rationale |
|---|---|---|
| EDR Visibility | High | The attack involves process execution (node, bun), file creation and deletion, network DNS queries, and curl/wget invocations spawned from Node.js — all of which are commonly captured by EDR telemetry. The preinstall hook execution from a package manager ancestor is a distinctive behavioral signal. |
| Network Visibility | Medium | DNS queries to Ethereum RPC providers (go.getblock.io, eth.llamarpc.com, eth-mainnet.nodereal.io) and C2 domains (npm-cache.com, awqhnjewqjkl.icu) are observable, but the use of legitimate blockchain RPC infrastructure and dynamic C2 resolution via smart contracts makes static domain blocking ineffective. |
| Detection Difficulty | Moderate | Process-level detection of node/bun executing specific script names is straightforward, but the dynamic C2 resolution via Ethereum smart contracts and abuse of legitimate RPC providers complicates network-based detection. The obfuscated payload and use of legitimate bun runtime downloads require behavioral rather than signature-based detection. |
Required Log Sources
- Endpoint process execution logs (process name, command line, parent process)
- DNS query logs
- Network connection logs
- File creation and deletion events
- Package manager installation events
Hunting Hypotheses
| Hypothesis | Telemetry | ATT&CK Stage | FP Risk |
|---|---|---|---|
| Consider hunting for Node.js or bun processes executing scripts named setup.mjs, Math_Symbol.js, or math_init.js, particularly when spawned by a package manager process as an ancestor | EDR process telemetry with parent-child relationships and command line arguments | Execution | Low — these specific script names combined with package manager ancestry are highly indicative of the CHAINDROP worm |
| Consider hunting for DNS queries to Ethereum RPC provider domains (go.getblock.io, eth.llamarpc.com, eth-mainnet.nodereal.io) originating from node or bun processes, as this indicates smart contract-based C2 resolution | DNS query logs correlated with process information | Command and Control | Medium — legitimate blockchain development may query these RPC providers, but correlation with node/bun process context reduces false positives |
| Consider hunting for curl or wget processes spawned by Node.js, which would indicate the dropper downloading the bun runtime from GitHub releases | EDR process telemetry showing curl/wget with node as parent process | Ingress Tool Transfer | Low to Medium — some legitimate build scripts may invoke curl from node, but this specific pattern is uncommon |
| Consider hunting for git commits with author name 'claude' and commit message 'chore: update config' across repositories, as these are worm-generated commit signatures | Git commit logs, CI/CD pipeline logs, or GitHub audit logs | Propagation | Low — the combination of author name and commit message is a specific worm indicator |
| Consider hunting for bun or node processes making DNS requests immediately followed by script execution and then deletion of temporary directories, indicating payload execution and defense evasion | EDR process and file event telemetry correlated over short time windows | Defense Evasion | Low — the sequence of download, execute, delete is characteristic of the CHAINDROP execution chain |
Control Gaps
- Static domain blocking is ineffective due to dynamic C2 resolution via Ethereum smart contracts and abuse of legitimate RPC providers
- Signature-based AV may miss the heavily obfuscated 711KB payload using Base91 encoding and control-flow flattening
- Network security controls may not flag traffic to legitimate blockchain RPC providers used as C2 resolution fallbacks
- IDE-based infection paths via .claude/settings.json and .vscode/tasks.json may bypass traditional endpoint controls focused on package installation
Key Behavioral Indicators
- Node.js process executing setup.mjs with package manager as ancestor process
- Bun process executing Math_Symbol.js or math_init.js as command-line arguments
- Curl or wget spawned by Node.js parent process (downloading bun runtime)
- DNS queries to Ethereum RPC providers from node or bun processes
- Node script execution followed immediately by deletion of the temporary directory
- Git commits authored by 'claude' with message 'chore: update config'
- Presence of .claude/settings.json with SessionStart hook or .vscode/tasks.json with folderOpen task executing setup.mjs
- npm preinstall hook entries in package.json executing arbitrary commands
False Positive Assessment
Low — The specific combination of preinstall hook execution, bun runtime download, obfuscated payload execution, and DNS queries to Ethereum RPC providers creates a highly distinctive behavioral pattern. Individual indicators like node executing scripts or DNS queries to blockchain providers may generate false positives in blockchain development environments, but the full execution chain is highly indicative of CHAINDROP activity.
Recommendations
Immediate Mitigation
- Verify against your organization's incident response runbook and team escalation paths before acting. Consider identifying and removing all compromised npm packages (keyv, flat-cache, cacheable-request, cacheable, cache-manager, and 400+ others) from package-lock files and CI caches
- Consider revoking all GitHub tokens (PATs, session tokens) and npm tokens on any machines that may have installed affected packages
- If applicable, check GitHub repositories for unauthorized commits with message 'chore: update config' from author '[email protected]' and remove malicious .claude/settings.json and .vscode/tasks.json hooks
- Consider rotating any credentials that may have been exposed on affected machines, including cloud provider credentials, Kubernetes service account tokens, Vault tokens, AI tooling API keys, and SSH private keys
Infrastructure Hardening
- Consider upgrading to npm 12 or later, which blocks preinstall hooks by default
- Evaluate enabling 2FA on all npm accounts and avoiding automation tokens with bypass_2fa enabled
- Consider implementing a soak period for new package versions before adopting them in build pipelines, allowing time for community detection of compromises
- If supported by your tooling, consider implementing package allowlisting or pinning with integrity hashes in production environments
- Evaluate whether your network security controls can detect and alert on DNS queries to blockchain RPC provider domains originating from developer workstations
User Protection
- Consider deploying EDR detection rules for Node.js pre/post-install script execution and package manager ancestry alerts
- If your EDR supports it, consider enabling behavioral detections for curl/wget spawned by Node.js processes
- Evaluate whether endpoint controls can detect and block the execution of bun runtime executing JavaScript payloads from temporary directories
- Consider scanning developer machines for the presence of Math_Symbol.js, math_init.js, and setup.mjs files in npm cache or node_modules directories
Security Awareness
- Consider reminding developers not to immediately adopt new package versions and to review preinstall hooks in package.json before installation
- Consider training developers to recognize and report suspicious git commits with unusual author names or generic commit messages like 'chore: update config'
- Consider raising awareness about the risk of opening unfamiliar repositories in VS Code or Claude Code, as malicious tasks.json and settings.json hooks can execute code automatically
- Consider educating developers on the importance of enabling 2FA on npm accounts and the risks of automation tokens with 2FA bypass
MITRE ATT&CK Mapping
Resource Development
Initial Access
Execution
Credential Access
Collection
Command and Control
Additional IOCs
- File Paths:
Math_Symbol.js- Payload filename in packages compromised directly from the keyv monorepomath_init.js- Payload filename in packages trojanized during worm propagation to other maintainers; shares same SHA-256 as Math_Symbol.jssetup.mjs- Dropper script injected into npm packages via preinstall hooks; also placed at .claude/setup.mjs and .vscode/setup.mjs for IDE-based infection paths.claude/settings.json- Malicious SessionStart hook injected by worm to execute setup.mjs when a Claude Code session starts.vscode/tasks.json- Malicious folderOpen task injected by worm to execute setup.mjs when an infected repository is opened in VS Code
- Command Lines:
- Purpose: Execute the CHAINDROP dropper script during npm package installation via preinstall hook | Tools:
node| Stage: Execution |node setup.mjs - Purpose: Execute the CHAINDROP dropper when a Claude Code session starts in an infected repository | Tools:
node| Stage: Execution |node .claude/setup.mjs - Purpose: Execute the CHAINDROP dropper when an infected repository is opened in VS Code | Tools:
node| Stage: Execution |node .vscode/setup.mjs - Purpose: Execute the obfuscated credential-harvesting payload using the bun JavaScript runtime | Tools:
bun| Stage: Execution |bun Math_Symbol.js - Purpose: Execute the obfuscated credential-harvesting payload variant using the bun JavaScript runtime | Tools:
bun| Stage: Execution |bun math_init.js
- Purpose: Execute the CHAINDROP dropper script during npm package installation via preinstall hook | Tools:
- Other:
thebeautifulmarchoftime- Cryptographically signed GitHub commit marker used as fallback C2 resolver when Ethereum smart contract query fails; validated against embedded RSA public keyclaude- Git commit author name used by worm-generated commits to identify automated propagationchore: update config- Git commit message used by worm-generated commits during package backdooringIfYouBlockThisAPIKeyItWillCrashTheLiveProductionServersOfAllThirdPartyClients- Intimidation string embedded in worm commit messages alongside stolen GitHub token to discourage revocation