Unit 42 researchers have published a detailed analysis of ChainDrop, a self-propagating npm worm that infected over 400 packages downloaded hundreds of millions of times weekly, including widely used packages like keyv and cacheable-request. The worm uses a preinstall lifecycle hook to drop an obfuscated 727 KB JavaScript payload via the Bun runtime. It harvests cloud credentials, npm/GitHub tokens, SSH keys, AI tool configs, and even scrapes GitHub Actions runner process memory for ephemeral OIDC tokens. Stolen npm tokens are used to republish infected versions of every package the victim account can publish, preserving legitimate functionality. Persistence is established through cross-linked VS Code tasks.json and Claude Code settings.json hooks. C2 routing is handled via an Ethereum smart contract (allowing silent domain rotation with a single on-chain transaction), with a GitHub commit dead-drop fallback. Stolen tokens are published in public commit messages with a distinctive marker string. A sophisticated secondary path targets the opensearch-js repository specifically, minting genuine Sigstore/SLSA provenance for the malicious package. The payload used three nested obfuscation/encryption layers. Unit 42 identified 453 public GitHub repositories across five victim accounts and detected execution in 10 distinct environments. Detailed IOCs, hunting queries, and remediation guidance are provided.
Table of contents
Indicators and Behavior of the ChainDrop WormEverything It StealsIt Reads CI Runner MemoryPersistence MechanismsPropagation and ExfiltrationIt Was Waiting for One Specific RepositoryThe Blockchain RouterThe Server Can Answer BackEncrypted ExfiltrationIt Publishes Stolen Tokens in Public Commit MessagesWe Followed the MoneyC2 Domain Rotation via Ethereum Smart ContractIs This Shai-Hulud?Detecting ChainDropBreaking Through Obfuscation and Encryption to Reach the PayloadQuestions this post answers
How does the ChainDrop npm worm use an Ethereum smart contract for C2 communication?
ChainDrop stores its C2 domains in an Ethereum smart contract (0xE1f2395ee43e45A1556EC6438a88c31B83493103) — a small StringListStore with getter and setter functions. The worm rotates through roughly 60 public Ethereum RPC endpoints to query the contract, making it resilient to any single provider blocking it. The operator can silently rotate all C2 domains with a single on-chain transaction, requiring no update to deployed malware. A GitHub commit dead-drop using the marker 'thebeautifulmarchoftime' serves as a fallback. Teams hardening npm pipelines against supply chain threats track emerging C2 techniques like this on daily.dev.
How does the ChainDrop npm worm steal secrets from GitHub Actions runners?
ChainDrop embeds a Python helper inside an encrypted blob that locates the Runner.Worker process on GitHub Actions runners, then reads /proc/<pid>/maps and /proc/<pid>/mem to search live process memory for OIDC tokens and runner secrets. This approach captures ephemeral credentials — including those designed to vanish when a job finishes — without waiting for secrets to be written to disk, bypassing file-based detection. Engineers securing GitHub Actions pipelines against credential theft find the latest attack techniques on daily.dev.
How does the ChainDrop npm worm establish persistence on developer machines?
ChainDrop writes a .vscode/tasks.json file with an 'Environment Setup' task that runs automatically when a developer opens the project in VS Code, and a .claude/settings.json file with a SessionStart hook that fires whenever Claude Code starts a session. Each file runs the dropper copy stored in the other tool's directory (.claude/setup.mjs and .vscode/setup.mjs respectively), creating cross-linked persistence. Removing either directory alone breaks both paths; all five dropped files must be deleted. Developers auditing project directories for unexpected config files can follow ChainDrop coverage and similar threats on daily.dev.