Executive Summary
A self-propagating npm worm nicknamed ChainDrop infected over 400 packages that are collectively downloaded hundreds of millions of times each week. This includes malicious versions of widely used packages such as keyv and cacheable-request. Unit 42 has unique observations of this attack.
The attackers behind ChainDrop potentially exposed developer workstations, continuous integration (CI) pipelines, cloud environments and downstream software users across a large number of organizations.
Once installed, ChainDrop steals:
- Cloud credentials
- npm and GitHub tokens
- SSH keys
- Other sensitive developer data
It can also extract temporary credentials from GitHub Actions runner memory and use stolen npm publishing tokens to infect and republish additional packages while preserving their legitimate functionality.
We have observed active attempted operations, which were detected out of the box by our existing products.
During our investigation into this attack, we identified 453 public GitHub repositories across five accounts matching the worm’s exfiltration patterns. We also detected ChainDrop execution across 10 distinct environments. At the time of publication, these repos were removed.
We have deobfuscated the malware and identified:
- Persistence through developer and AI coding tools
- Blockchain-based command-and-control (C2) resolution
- Its ability to execute additional attacker-supplied code
Additionally, late on Aug. 4, 2026, we observed the adversary silently reconfiguring the worm's entire C2 infrastructure through a single Ethereum transaction, without requiring any update to the deployed malware.
This attack is the latest in a series of threats to the security of the npm ecosystem.
Unit 42 recommends:
- Identifying installations of affected npm package versions
- Removing affected package versions
- Investigating developer workstations and CI runners for signs of compromise
- Reviewing unexpected npm publishing and GitHub repository activity.
- Revoking and rotating potentially exposed npm, GitHub, cloud, SSH and automation credentials.
- Removing identified persistence mechanisms
- Blocking both the domain-based and GitHub-based exfiltration channels
The Koi Agentic Endpoint Security risk engine flagged the malicious package activity as the attack unfolded. Cortex XDR detected and alerted on the worm’s execution using out-of-the-box behavioral detections.
Palo Alto Networks customers can use Koi Agentic Endpoint Security to help identify and control malicious packages across developer endpoints.
The Cortex AgentiX Threat Intel agent can help allow analysts to extract, enrich, and search IoCs using natural language to quickly determine organizational impact.
Cortex Cloud Endpoint Protection leverages AI-enabled analytics to help detect and prevent threats targeting Linux endpoints, containers, and associated cloud IAM policies.
Cortex XDR and XSIAM provide behavioral detection, investigation and response that can help organizations address ChainDrop activity executing in development environments.
Idira Secrets Manager and Secrets Hub eliminate hard-coded credentials from configure files and source code by automating zero-downtime rotation, and dynamically delivering just-in-time access to non-human identities across multi-cloud and DevOps environments.
The Unit 42 Cloud Security Assessment is an evaluation service that reviews cloud infrastructure to identify misconfigurations and security gaps.
The Unit 42 Incident Response team can also be engaged to help with a compromise or to provide a proactive assessment to lower your risk.
| Related Unit 42 Topics | AI, Malware, Supply Chain, npm Packages |
Details of the ChainDrop npm Worm
Indicators and Behavior of the ChainDrop Worm
We analyzed the contents from one of the infected packages to understand the full attack chain.
The package contained the legitimate software development kit (SDK) code that a user would expect, including the source, dependencies and documentation. But it also contained small indicators of the ChainDrop worm: two extra top-level files and one lifecycle hook.
The indicators of the worm can be subtle, as illustrated in the following example.
One of the indicators is an infected npm package's package.json file containing code with the preinstall command, as shown in Figure 1.
That preinstall line is the only modification the worm makes to this package's manifest. It points to setup.mjs, a dropper that checks whether Bun (a lightweight JavaScript runtime and package manager alternative to Node.js) is on PATH. It downloads Bun 1.3.13 from the legitimate Oven GitHub repository if it isn't present. Then it feeds Bun a 727 KB obfuscated JavaScript payload (math_init.js) compressed into two source lines.
To be clear: Bun is not compromised. The attacker is using a legitimate runtime as a portable execution vehicle.
The payload spawns a detached background process, sets _NODE_RUNTIME_INIT=1 to prevent recursive relaunch and lets the install finish cleanly. No errors. No warnings.
Most developers would move on without noticing a key detail: The worm is already running.
The worm detaches when it is not in CI. If it detects a CI environment it runs inline in the job instead, which means its own debug output lands in the workflow log. This is useful for defenders looking for indicators because the worm is chatty.
One further gate runs before the worm engages in any collection. This gate is a locale check that, on a Russian-language host, prints “Exiting as russian language detected!” and exits cleanly. The worm spares those machines.
Everything It Steals
The background payload begins a sweep of the infected machine to harvest credentials from the environment. These include the following categories:
- Cloud credentials:
- Multiple major cloud infrastructure platforms
- The worm queries metadata endpoints and token endpoints across both compute instances and container services to harvest temporary identity and access management (IAM) role credentials, extending scope to short-lived identity tokens used by automated integration runners
- Multiple major cloud infrastructure platforms
- Developer tooling:
- Docker and Helm configurations
- Git credentials
- Mount listings
- npm and GitHub tokens
- Poetry and PyPI credentials
- RubyGems tokens
- SSH keys
- Terraform state
- Vault tokens
- AI tools:
- AI-assisted coding tools
- Cloud-based development platforms
- Open-source coding assistant configurations and authentication artifacts
- Everything else:
- .env files
- .netrc
- Application configuration scattered across the home directory
- Bitcoin and Electrum wallet files
- Jenkins encrypted credential material
- Kubernetes service-account tokens and kubeconfigs
- Shell histories
ChainDrop harvests credentials, but also a wide variety of other information about the systems and environment it’s running on.
Some of the information stolen is vital for the worm’s survival. The npm and GitHub tokens it finds are what it needs to keep spreading.
It Reads CI Runner Memory
An embedded Python helper hidden inside an encrypted blob in the payload locates the Runner.Worker process on GitHub Actions runners, opens /proc/<pid>/maps and /proc/<pid>/mem, and searches live process memory for OpenID Connect (OIDC) tokens and runner secrets.
The flow of this GitHub Actions runner memory scraping is illustrated in Figure 2.
Rather than waiting for a file to be written to disk, the worm searches memory. In the process, it captures secrets that may have been designed to vanish when a job finishes.
Organizations should be aware that CI runners are credential targets and can be exfiltrated through attacks on process memory.
Persistence Mechanisms
The worm establishes several persistence mechanisms, but two of them deserve special attention:
- Cross-linked persistence through VS Code and Claude Code
- A latent capability for OS-level persistence
It writes a .vscode/tasks.json file with a task labeled Environment Setup and sets it to run when the folder opens — meaning it executes automatically whenever a developer opens the project in VS Code. That task runs node .claude/setup.mjs, a copy of the dropper that is byte-identical to the setup.mjs shipped in the package itself.
It also writes a .claude/settings.json file with a SessionStart command hook, meaning it executes whenever Claude Code starts a session in the project. That hook runs node .vscode/setup.mjs, a second copy of the same dropper.
Figure 3 shows the cross-linked persistence through both .vscode/tasks.json and .claude/settings.json files.
|
1 2 3 4 5 6 7 |
//Title: Chaindrop worm payload execution via bun runtime //Description: Chaindrop (Shai Hulud variant) executes math_symbol.js or math_init.js via the bun nodejs runtime to harvest credentials, plant .claude and .vscode persistence, and exfiltrate the information. // MITRE ATT&CK TTP ID: T1059.007 dataset=xdr_data |filter event_type=ENUM.PROCESS |filter action_process_image_name in("bun","bun.exe")and(action_process_image_command_line contains"Math_Symbol.js"oraction_process_image_command_line contains"math_init.js") |fields agent_hostname,agent_id,causality_actor_process_command_line,actor_process_image_name,actor_process_command_line,action_process_image_command_line,event_id,actor_effective_username |
|
1 2 3 4 5 6 7 8 9 |
//Title: Chaindrop worm GitHub credential harvesting via gh CLI //Description: Chaindrop (Shai Hulud variant) payload running under the bun nodejs runtime invokes "gh auth token" to extract the local GitHub CLI authentication token, used for repository persistence commits and worm propagation. // MITRE ATT&CK TTP ID: T1528 dataset=xdr_data |filter event_type=ENUM.PROCESS |filter actor_process_command_line contains".js"andactor_process_image_name in("bun","bun.exe")andaction_process_image_command_line contains"gh auth token" |dedup agent_id,actor_process_command_line,action_process_image_command_line,action_file_path |fields agent_id,agent_hostname,actor_process_command_line,actor_process_image_name,actor_process_image_sha256,action_process_image_name,action_process_image_command_line,action_process_image_sha256,causality_actor_process_image_name,causality_actor_process_command_line,causality_actor_process_image_sha256,action_file_name,action_file_path,action_file_sha256 |sort asc _time |