The Node Supply Chain Problem

, 6 minutes to read

You know that feeling when you run npm install and watch 47,000 packages fly by your terminal Matrix-style, and you think, “Surely all of these are legitimate and not at all suspicious”? Well, of course it isn’t always like that. Unfortunate, but a fact of life.

When Packages Attack

In September 2025, the NPM ecosystem experienced what can only be described as an awful Monday. A self-replicating worm, charmingly named “Shai-Hulud” (after the giant sandworms from Dune1, because hackers are nerds too)2, compromised over 500 packages. The crown jewel of this chaos? The popular @ctrl/tinycolor package, which pulls in over 2 million weekly downloads, got pwned along with 40+ of its friends.

Here’s how this digital nightmare unfolded: The attack likely started with a phishing campaign that spoofed NPM and tricked developers into updating their MFA login options. Classic move3. Once inside, the malware did what any self-respecting worm would do—it got greedy.

The malicious code would scan your environment for sensitive credentials (NPM tokens, GitHub personal access tokens, AWS/GCP/Azure API keys), exfiltrate everything to an actor-controlled endpoint, then create a public GitHub repository called “Shai-Hulud” under your account and commit all your secrets there4. After thoroughly embarrassing you, it would use your stolen NPM token to authenticate as you, inject malicious code into other packages you maintain, publish those compromised versions, and watch the chaos spread exponentially.

It’s like if your house key could photocopy itself, break into your neighbour’s house, steal their keys, and repeat the process. Automated, self-replicating credential theft at scale. What a time to be alive.

The implications? Oh, just minor things like data theft, ransomware deployment, crypto mining, deletion of production environments, and database theft. You know, Tuesday stuff.

A Brief History of “Oops, All Malware”

This wasn’t NPM’s first rodeo. The ecosystem has been playing whack-a-mole with supply chain attacks for years. Remember when someone hijacked the event-stream package and tried to steal Bitcoin? Or when the ua-parser-js package got compromised to mine cryptocurrency? The NPM registry is basically a trust-fall exercise where occasionally someone doesn’t catch you and instead steals your AWS credentials5.

What makes the Shai-Hulud incident special is its self-replicating nature. Previous attacks required manual work to compromise each package. This one? It’s the supply chain attack that keeps on giving.

How to Solve Those Things

NPM audit: The Bare Minimum

The npm audit command is your first line of defence. It submits your dependency tree to the NPM registry and asks, “Hey, are any of these packages actively trying to ruin my life?” It uses a Bulk Advisory endpoint to check for known vulnerabilities and can even calculate meta-vulnerabilities—vulnerable packages because they depend on vulnerable packages. Vulnerabilities all the way down6!

You can run npm audit fix to automatically remediate issues, though some vulnerabilities require manual intervention (read: you actually have to think about it).

pnpm: The Paranoid Sibling

pnpm v10 took a bold stance: disable postinstall scripts by default. Why? Because, historically, most compromised packages used postinstall scripts to run malicious code immediately upon installation. Revolutionary idea: don’t automatically run random code from the internet.

pnpm also offers a minimumReleaseAge setting. Set it to 1,440 minutes (24 hours) or 10,080 minutes (one week), and pnpm won’t install packages until they’ve aged a little (and some other sucker fell for it). Since malware is usually detected quickly, this delay can save you from installing new compromised versions. It’s the software equivalent of waiting to see if your friends get food poisoning before ordering the same dish7.

And I do generally think—ignoring all the additional security features of pnpm—it is a better package manager.

GitHub’s Master Plan

GitHub is rolling out some actual security improvements. On the authentication front, local publishing now requires 2FA (no more “oops, I forgot”), granular tokens come with a 7-day lifetime8 (because forever tokens are how we got here), and they’re pushing for trusted publishing (because maybe we shouldn’t rely on tokens at all).

They’re also deprecating legacy classic tokens (RIP, you won’t be missed), moving from TOTP to FIDO-based 2FA (because your authenticator app can be phished too), making publishing access disallow tokens by default, and removing the ability to bypass 2FA for local publishing. It’s almost like they learned something from this mess.

Other Wisdom from the Security Trenches

CISA’s playbook recommends pinning your npm package versions to known safe releases (before 16 September 2025, in this case), rotating ALL developer credentials immediately (yes, all of them), using phishing-resistant MFA, and enabling GitHub Secret Scanning and Dependabot. You know, just some light weekend security work.

Palo Alto Networks suggests using lock files (and actually committing them)9, verifying package integrity with strong hash algorithms (SHA-1 is so 2011), removing unused dependencies (each one expands your attack surface—I will have to write about Knip at some point), and referencing git packages by commit hash, not branch. Branches can be force-pushed. Commit hashes are forever.

The Corporate Solutions

If you have enterprise money, tools like Cortex Cloud’s SBOM functionality can detect malicious packages before, during, and after builds. They use operational risk models to flag suspicious behaviour, like sudden new versions from previously stable packages. Though, arguably, if you need a dedicated security product to tell you that left-pad suddenly requiring network access is suspicious, we might have bigger problems10.

The Moral of the Story

The NPM ecosystem is a beautiful, chaotic mess of trust. We trust that maintainers are who they say they are. We trust that packages do what they claim. Likewise, we trust that those 47,000 dependencies aren’t collectively planning our demise.

But trust requires verification. Lock your dependencies. Audit your packages. Use 2FA. Delay updates. Maybe don’t automatically execute code from strangers on the internet (why did we ever think that was a good idea?).

And for the love of all that is holy, commit your lock file.


  1. This is according to the internet; I haven’t read or watched Dune. ↩︎

  2. The hackers continue to have better naming than most AI startups. ↩︎

  3. If you’ve ever clicked “Update Payment Info” in a suspiciously urgent email, congratulations—you’ve been phished. Welcome to the club; we meet on Tuesday mornings. ↩︎

  4. You know, just casually exposing them to the entire internet. No big deal. ↩︎

  5. This metaphor got away from me, but you get the idea. ↩︎

  6. It’s turtles all the way down, except the turtles are CVEs. ↩︎

  7. This is called “defensive package management,” or “learning from other people’s mistakes,” which is generally a good life strategy. ↩︎

  8. I assume quite a bit of damage can be done with a 7-day token, but at least the horror show is automatically over in a week. ↩︎

  9. The number of production systems I’ve seen without committed lock files is too damn high. Stop deploying Schrödinger’s dependencies. ↩︎

  10. To be fair, in 2025 this probably describes at least one legitimate NPM package. ↩︎

Tags: Cybersecurity, Productivity, Technical, Typescript