

The Shai-Hulud worm is the latest in a long line of supply chain attacks. And with it comes the usual chatter about run-time defenses, vendor quick fixes, and IOCs.
The missing piece is ownership. When teams pull in open source packages, they need to own them.
Taking ownership isn't easy.
Prefer video? I break this down in my YouTube video: How to Use npm Packages Safely and Avoid Supply Chain Issues
For an addict, the first step is admitting a problem has become unmanageable. They describe the moment when the pain of the problem outweighs the pain of the solution.
Security is at a similar point. As an industry, we’ve grown addicted to easy fixes. Need utilities for arrays and strings? Pull in lodash. Need real-time client-server communication? Install Socket.IO.
Using third-party components isn’t the problem. There’s no need to reinvent the wheel. Many are elegant, efficient, and proven. The problem is that we’ve normalized blind trust. We use them without verifying.
Most open source developers are honest, and doing their best to share useful tools. But they face the same limits and burnout as everyone else. And when their projects succeed, the pressure grows.
Any time we use someone else’s code, there’s risk. We like to think open source is safer because it’s public. Every commit gets reviewed and every pull request approved. Right?
Right????
A 2022 study showed otherwise. Researchers looked at open source packages to see how often maintainers reviewed changes. Reviews matter because they can block malicious commits from rogue or compromised accounts. The results were bleak: only 9% of packages had reviews. npm was worst of all with just 5 out of 918 packages (0.5%) reviewing every update.
The problem is clear and painful. An attacker can inject malicious code by taking over one maintainer’s account. The answer is ownership: verify the code yourself before you use it.
Here are three steps to make that happen.
When I was at Amazon, ownership meant something specific. If you wanted a third-party package, you didn’t just pull it in. You forked it into an internal repo, made sure it was secure, and kept it up-to-date yourself.
Code reviews can be simple or thorough, depending on your needs. Start with the tools you already have. Static analysis isn’t perfect, but it’s a good first step. The issues it flags often point to code that deserves a closer look. Combining reviews with these tools can be very effective.
AI can help here too. I built a small Claude Code subagent to test the idea. It did a solid job listing conditions to check and scanning both the code and its dependencies. It won’t be perfect, but when combined with other tools it’s good enough and far better than nothing.
The goal was to see if it could work. It can. You can build one yourself, or grab mine here:
After you’ve reviewed a package, lock it to that version. If you don’t, a malicious update could slip in automatically.
Npm handles packages and versions through the package.json file. That's where you tell npm to pin your version or allow updates. It lets you accept updates in different ways. In fact, there are several ways to do the same thing. Here’s an example:
(Note: JSON doesn't allow comments or duplicate keys. This is an educational snippet that won't run in real life)
// ======= PIN TO EXACT VERSION
// Installs exactly [email protected] (no updates)
"js-cookie": "2.1.2",
// ======= PATCH-ONLY UPDATES (stay on 15.8.x)
// Allow updates to latest 15.8.x for prop-types, but never jump to 15.9
"prop-types": "~15.8.1",
"prop-types": "15.8.x",
"prop-types": "15.8.1 - 15.8.999"
// ======= MINOR-LEVEL UPDATES (stay on 4.x.x)
// Allow updates to latest 4.x.x for lodash, but never jump to 5.x
"lodash": "^4.0.0",
"lodash": "4.x",
"lodash": "4.*",
"lodash": "~4",
"lodash": ">=4.0.0 <5.0.0"
"lodash": "4.0.0 - 4.999.999" You can pin a package to the exact version you reviewed. But that doesn’t solve everything, because each package may depend on others. Those dependencies can still update on their own.
Enter package-lock.json
The package-lock.json file sits alongside package.json. When you run npm install, it records the exact versions of every package in your project. It also records the commit hashes, so nothing can change upstream.
Its job is to lock every version in place, no matter where the code runs. That’s the key to preventing unwanted updates. And when you deploy, use npm ci to install exactly what’s in the lock file.
Remember:
• npm install adds a package and updates the lock file.
• npm ci installs exactly what’s in the lock file.
I hesitate to say “turn off auto updates.” Too many teams already avoid updates altogether, and that’s just as dangerous.
I’m not saying stop updating. I’m saying make updates intentional. Don’t let npm handle them for you. Instead, use a tool like Dependabot or Renovate. They scan for outdated packages and raise pull requests for you.
That difference matters. A pull request lets you run your tests and security scans, and it requires a review before merging. That’s a far safer way to update.
finally:
Nothing changes if nothing changes. If we want to change the trajectory of supply chain attacks, teams need to take ownership of the code they use. Review the code. Pin the versions. Monitor and update. Own it, or attackers will.
Watch the companion video: How to Use npm Packages Safely and Avoid Supply Chain Issues (live today on my YouTube channel).
Read the research article: Are your dependencies code reviewed?
