JD Tech Consulting
All writing
Security3 min read

Your dependencies are the attack surface now.

A self-replicating worm tore through npm twice in 2025 — stealing credentials, spreading through trusted packages, even wiping machines when cornered. If one install runs an attacker's code with your keys, your supply chain is your perimeter.

By John D.

A lattice of connected nodes representing software packages, with one infected node rippling corruption through the network.

Illustration generated for JD Tech Consulting

Most teams think of their attack surface as the code they wrote. It hasn't been that for a long time. The average app is a thin shell of your code wrapped around hundreds of dependencies you didn't write, pulled from a registry you don't control, installed by a command that runs whatever those packages tell it to. In 2025, npm got a very loud reminder of what that means.

What happened

In September, a self-replicating worm the community named Shai-Hulud tore through npm, compromising over 500 packages. The mechanics are worth understanding, because they're the whole point:

  • A compromised package, on install, ran code that scanned the machine for credentials — GitHub tokens, AWS/GCP/Azure keys, npm tokens.
  • It exfiltrated those secrets, including by committing them to a public GitHub repo under the victim's own account.
  • Then it used any stolen npm token to inject itself into other packages that developer maintained and republish them — so the worm spread on its own, maintainer to maintainer, no attacker required.

Then it got worse. In November, Shai-Hulud 2.0 hit — bigger and nastier. Researchers counted hundreds of packages backdoored with tens of millions of weekly downloads, tens of thousands of malicious repositories, and thousands of secrets exposed across hundreds of organizations. The upgrades were pointed:

  • It ran during the pre-install phase, so it fired even if the install ultimately failed.
  • It used the Bun runtime to sidestep Node-focused monitoring.
  • It planted persistent backdoors via self-hosted CI runners.
  • And it shipped a dead man's switch: if it couldn't find credentials to steal, it tried to wipe the user's home directory. Nothing to take? Burn it down.

The likely entry point was mundane — a phishing email telling developers to "update their npm MFA."

Why this should worry a non-engineer too

You don't have to run npm to have this problem. Every modern stack has an equivalent — PyPI, RubyGems, Maven, container base images, GitHub Actions, browser extensions. The shape is universal: you trust code by transitive reputation, and installing it executes it with your privileges.

A dependency isn't a document you read. It's a program you run — usually with your cloud keys sitting in the same environment. Treat every npm install in CI as "run this stranger's code against our secrets," because that's what it is.

The reason this class of attack is so effective is leverage. An attacker doesn't breach 500 companies. They compromise one popular package, and 500 companies install the breach themselves, on schedule, in their build pipelines, with production credentials in scope.

What I'd actually do

You can't audit every transitive dependency by hand, and you don't need to. You need to make a compromised package far less useful to the attacker.

  1. Kill install scripts by default. Most packages don't need lifecycle scripts to install. Run npm ci --ignore-scripts in CI and allowlist the few that genuinely need them. This alone defangs the most common infection path.
  2. Pin everything and add a cooldown. Commit lockfiles, pin exact versions, and don't auto-upgrade to a release that's hours old. A short "let a version bake" policy would have dodged both waves, which were caught within hours.
  3. Get your secrets out of the build. CI tokens should be short-lived, narrowly scoped, and issued via OIDC — not long-lived keys sitting in environment variables for any install script to scoop up. Assume anything present in the build can be stolen.
  4. Turn on the free defenses. GitHub secret scanning with push protection, Dependabot alerts, and branch protection. A worm that commits stolen secrets to a public repo gets caught fast if scanning is on.
  5. Know what you ship. Maintain an SBOM and use dependency/supply-chain scanning (Socket, Dependabot, Snyk, or similar) so a malicious version raises a flag before it reaches production.
  6. Have a rotation runbook. When — not if — a dependency you use is compromised, you need to rotate the affected tokens and keys in minutes, not spend a day figuring out which ones were exposed.

The bottom line

The Shai-Hulud waves weren't exotic nation-state wizardry. They were a phishing email, a stolen token, and a registry that runs code on install — automated into a worm. That combination isn't going away, and it isn't unique to npm.

Stop treating dependencies as free infrastructure you can trust by default. They're untrusted code you execute on purpose, and the least you can do is make sure that when one turns hostile, it can't walk out with your keys.

  • supply chain
  • npm
  • open source
  • CI/CD
  • secrets

Get in touch

Let's talk.

Tell us what you're working on. We'll take it from there.