← all posts
// security · security

ChainDrop: the npm worm that ships with valid signatures

Since August 4 a self-propagating worm has been moving through npm. Security teams at Wiz, Expel and Datadog track it as part of the Mini Shai-Hulud family and call the current wave ChainDrop. The entry point was a hijacked GitHub account belonging to the maintainer jaredwray, which is how malicious versions of keyv, cacheable, flat-cache, file-entry-cache and other packages reached the registry: roughly 2,236 affected versions in total. The detail that should change how you think about your CI is that those packages carried valid signatures. They were built and published by the maintainer's own GitHub Actions pipeline, which the attacker controlled.

How the payload runs

  • A preinstall script, setup.mjs, executes during npm install before any of your code runs.
  • It downloads the Bun runtime and uses it to execute an obfuscated file, Math_Symbol.js.
  • That script harvests npm tokens, GitHub tokens, cloud credentials and CI secrets from the environment.
  • With a publish credential in hand it pushes poisoned versions of whatever packages that token can publish, which is what makes it a worm rather than a one-off backdoor.

The packages involved are not exotic. keyv and cacheable sit under caching layers in a large fraction of Node projects, and flat-cache and file-entry-cache are pulled in transitively by common linting and build tooling. You can have all four in your tree without ever having typed their names.

Why signatures did not help

Provenance signing answers one question: was this artifact built by the pipeline it claims to be built by. It cannot answer whether the pipeline was operated by the right person. When an attacker owns the maintainer's GitHub account, the Actions workflow runs exactly as designed, signs exactly as designed, and publishes exactly what the attacker committed. Every check downstream sees a green badge. The same applies to a lockfile pin: pinning to a version published on August 6 pins you to the malware.

A valid signature proves the build was legitimate. It says nothing about whether the builder was.

This is also why the blast radius is CI rather than laptops. Build agents hold the richest credentials on the team and run npm install more often than anyone, on every push, unattended. If you let coding agents run installs inside your repos, the same exposure applies to them, which is one more argument for the isolation I described in sandboxing coding agents.

The checklist I ran this week

  • Inventory. Run npm ls keyv cacheable flat-cache file-entry-cache in every repo, including the ones you think are dormant. Then diff each lockfile against publish dates and treat any version of the affected packages published after August 4 as hostile until the advisories say otherwise.
  • Stop lifecycle scripts in CI. Switch to npm ci --ignore-scripts and add back the specific packages that genuinely need a postinstall step, by allowlist. Pin integrity hashes so a re-resolved lockfile cannot swap a tarball.
  • Rotate everything the build could see: npm tokens, GitHub tokens, cloud keys, CI secrets. Assume any runner that installed an affected version between August 4 and today leaked what it held.
  • Move your own publishing to OIDC trusted publishing and enforce 2FA on every maintainer account. Long-lived publish tokens in CI are the fuel this worm runs on.
  • Grep build logs for setup.mjs, Math_Symbol.js and any download of a Bun binary. A hit is an incident, not a curiosity.
  • Add a preinstall-script detector to your dependency review. A new lifecycle script in a patch release of a utility package should block a merge.

The honest limit

The 2,236 figure and the package list come from the vendors tracking the campaign, and it is still moving, so both are out of date the moment they are printed. Some of the mitigations have a cost: ignoring scripts breaks native modules that need a build step, and OIDC publishing takes real work to roll out across an organisation. None of that is a reason to wait. The worm's whole design is to live in the gap between when you hear about it and when you rotate the tokens.

#security#supply-chain#npm#ci