Skip to content

Back to the catalog

'ChainDrop' worm compromises hundreds of popular npm packages
securitylabs.datadoghq.comwriteup

'ChainDrop' worm compromises hundreds of popular npm packages

ChainDrop is an npm worm that compromised hundreds of packages on 4 August 2026, some with over 150 million weekly downloads, starting from a malicious commit (174f6a5) in the keyv repository. Datadog dissects the Bun-based loader, the obfuscated second stage, credential theft (files, GitHub Actions runner memory, AWS, Kubernetes, Vault), Ethereum smart-contract C2 discovery, AES/RSA exfiltration, an eval channel, and propagation via stolen npm tokens, GitHub branches and Sigstore provenance. IOCs and a list of affected packages are included.

TL;DR Datadog tears down the ChainDrop npm worm, covering its Bun loader, credential theft, Ethereum-based C2, propagation methods and IOCs.
Document

What it is

ChainDrop is a self-propagating npm worm. On 4 August 2026 Datadog found a malicious commit (174f6a5) in the keyv repository, followed minutes later by similar commits in jaredwray/cacheable and jaredwray/ecto. Hundreds of packages were compromised, some with over 150 million weekly downloads. The payload resembles the Shai-Hulud worm, and its fallback exfiltration repositories reuse the description "Shai-Hulud: Here We Go Again".

How it works

Compromised packages carry setup.mjs (an 11 KB loader) and math_init.js (the obfuscated second stage). The loader runs from "preinstall":"node setup.mjs". It uses any bun on the host, or downloads Bun 1.3.13 from GitHub releases without verifying it. Outside CI, the second stage detaches itself and holds a lock file named tmp.dpkg_14527.lock in the temp directory.

It collects credentials from:

  • Files: 290 Linux, 129 macOS and 50 Windows path patterns.
  • GitHub Actions runner memory: reads Runner.Worker through sudo python3 and /proc/<pid>/mem.
  • AWS: IMDSv2 only, across 17 commercial regions.
  • Kubernetes: in-cluster Secrets.
  • Vault.

With a classic GitHub token that has workflow scope, it pushes a branch dependabot/github_actions/format/setup-formatter containing .github/workflows/codeql_analysis.yml. That workflow dumps ${{ toJSON(secrets) }} to an artifact, which the worm downloads before deleting the run and branch.

flowchart TD
  A[Stolen data ready] --> B{eth_call to contract}
  B -->|healthy domain| E[AES-GCM + RSA-OAEP POST]
  B -->|fails| C{GitHub commit search}
  C -->|signed domain| E
  C -->|no domain, ghp_/gho_ token| D[Public Dune-named repo]
  E --> F[Response .code is eval'd]

C2 discovery works as follows:

  • Primary: the malware queries 73 Ethereum RPC endpoints with eth_call to contract 0xE1f2395ee43e45A1556EC6438a88c31B83493103, selector 0x53ed5143. It uses the first returned domain that answers /router with 400 or 404.
  • Fallback: it searches GitHub commits for thebeautifulmarchoftime (with trailing space). It then accepts the marker thebeautifulsnadsoftime followed by a Base64 domain and an RSA signature.
  • Last resort: it creates a public repo with results/results-<timestamp>-<counter>.json files. If the victim belongs to no GitHub organization, the token is double-Base64 encoded into the commit, marked with IfYouBlockThisAPIKeyItWillCrashTheLiveProductionServersOfAllThirdPartyClients, so the repo doubles as a token relay.

If the C2 returns HTTP 200 with JSON, the worm runs eval(responseBody.code). The response is unsigned, so the operator can run arbitrary JavaScript on infected hosts.

Propagation uses three paths:

  • npm: granular tokens with bypass_2fa: true and package-write permission. The worm republishes the latest tarball with the patch version bumped and scripts replaced entirely, which can break the victim's own build and test scripts.
  • GitHub branches: up to 50 branches per repo get .vscode/tasks.json, .claude/settings.json (a SessionStart hook), .claude/setup.mjs, .claude/math_init.js and .vscode/setup.mjs. Opening the folder in VS Code or starting a Claude session reruns the loader.
  • Provenance: this path runs only inside opensearch-js release-drafter.yml and targets @opensearch-project/opensearch. It adds the optional dependency @opensearch/setup pinned to github:opensearch-project/opensearch-js#d446803f4c3bc116263faa3499a1d3f95b2825de. Datadog says a valid Sigstore attestation here would bind the malicious build faithfully, so provenance is not a safety verdict.

Preconditions / Affected

  • The package manager must run dependency lifecycle scripts for the loader to start.
  • Exposed secrets are those reachable from the host's privileges. Environment-scoped GitHub secrets the injected job cannot access stay out of reach.
  • Any developer or CI machine that installed a compromised version should be treated as having leaked every credential on it.

What to do

The page's own remediation guidance is thin. It points to a continuously updated list of compromised packages (with a CSV in Datadog's GitHub repository), which the fetched text did not include. It also offers Datadog Code Security and the open source supply-chain firewall (SCFW), which blocks known malicious packages at install time and logs every npm and PyPI install.

Hunt for these indicators:

SHA-256 of exfil RSA public key (DER):
dc1e6a7ddb29390dd53cf1e5aac40ad9204ea7c6b83ef5656e7cb7a796808b67
SHA-256 of fallback-domain verification key:
166be2b7b58a440f7b17520ffb0368be5d89c76661704b4945417eb04b9ada65

<tmpdir>/tmp.dpkg_14527.lock
~/.local/bin/gh-token-monitor.sh
~/.config/gh-token-monitor/{token,handler,started_at}
~/Library/LaunchAgents/com.user.gh-token-monitor.plist
~/.config/systemd/user/gh-token-monitor.service
/tmp/gh-token-monitor.out.log
/tmp/gh-token-monitor.err.log

Other signs to look for:

  • Unexpected .claude/ or .vscode/ files in repositories.
  • Branches named dependabot/github_actions/format/setup-formatter.
  • A codeql_analysis.yml you did not write.
  • Public repositories with Dune-style names.
  • Packages whose scripts object was reduced to a single preinstall.

Caveats

  • The gh-token-monitor paths are described as dormant, so their absence does not clear a host.
  • The analysis is static. Datadog says it establishes the request sequence for the Sigstore path, not that Fulcio, Rekor or npm accepted any run.
  • The "Exiting as russian language detected!" string is dead code, so there is no active geofence.

Organisations named here

  • jaredwray/ecto

Technologies named here

  • Datadog Code Security
  • Bun
  • @opensearch-project/opensearch
  • Sigstore
  • npm
  • GitHub
  • GitHub Actions
  • Ethereum
  • keyv
  • cacheable
  • SLSA
  • Datadog Security Research Feed
  • Datadog supply-chain security firewall (SCFW)
  • GitHub REST Contents API
  • jaredwray/cacheable (GitHub repository, similar malicious commits)
  • jaredwray/ecto (GitHub repository, similar malicious commits)
  • keyv npm package (malicious commit 174f6a5 in its GitHub repository)
  • npm provenance / SLSA / in-toto

Techniques named here

  • Code obfuscation
  • EtherHiding
  • Blockchain smart-contract dead drop C2 discovery
  • Cloud and cluster secret collection
  • Credential harvesting from files
  • Detached background execution and single-instance lock
  • Hybrid encrypted exfiltration (gzip, AES-256-GCM, RSA-OAEP SHA-256)
  • IDE and AI assistant persistence via config files
  • Malicious GitHub Actions workflow secret exfiltration
  • Memory scraping of Runner.Worker via /proc/<pid>/mem
  • Public GitHub repository exfiltration and token relay
  • Remote code execution through eval of C2 response
  • Self-propagating worm via stolen npm tokens
  • Supply chain compromise via npm package poisoning

Threat actors named here

  • ChainDrop
  • Shai-Hulud

Also named here

  • setup.mjs
  • math_init.js