Supply chain attacks are evolving — here's how teams respond
A practical playbook for hardening your dependency graph without grinding shipping to a halt.
The most dangerous line of code in your application is one you didn’t write, didn’t read, and didn’t know was there. It arrived as a transitive dependency of a dependency of something you installed to format dates — and it has the same access to your build server and your users as the code you actually reviewed.
Supply chain attacks exploit exactly that gap. They don’t break down your front door; they get themselves added to your shopping list. Over the last few years they’ve gone from theoretical to routine, and the 2024 backdoor in xz-utils — caught by luck, days from shipping into mainstream Linux — showed just how patient and well-resourced the attackers have become.
How the attacks evolved
Early supply chain attacks were crude: upload a package called “reqeusts” and wait for a typo. The current generation is far more deliberate, and a handful of distinct playbooks now show up again and again.
- Typosquatting — a malicious package one keystroke away from a popular one.
- Dependency confusion — publishing a public package that shadows a company’s internal one, so the build grabs the attacker’s version.
- Account takeover — phishing or session-stealing a real maintainer, then shipping a poisoned update to everyone who already trusts the package.
- Malicious install scripts — a postinstall hook that runs arbitrary code on every machine that so much as installs the dependency.
- The long game — the xz approach: a fake contributor spends years earning maintainer trust, then lands a subtle backdoor in plain sight.
Why it’s hard to defend
The uncomfortable truth is that you run thousands of packages you have never read and never will. A typical Node project pulls in a dependency tree hundreds or thousands of modules deep; auditing all of it by hand is not a real option for any team that also intends to ship.
And the trust is transitive. You vetted your direct dependencies — maybe. But each of those trusts its own dependencies, and a compromise anywhere in that tree inherits the blast radius of the whole: your CI secrets, your signing keys, your users’ data.
You don’t install a package. You install everything that package trusts, and everything they trust, all the way down.
A practical playbook
You can’t read every line, but you can shrink the attack surface and slow attackers down enough that most of them move on to an easier target. The goal isn’t perfect safety; it’s making your project harder than the next one without grinding delivery to a halt.
- Commit your lockfile and install with integrity checks — use npm ci, not npm install, in CI so hashes are verified and nothing silently drifts.
- Disable install scripts by default and allow-list only the few packages that genuinely need them.
- Pin and review dependency updates with a bot like Dependabot or Renovate — automation to surface them, humans to approve them.
- Generate an SBOM so you can answer “are we affected? ” in minutes, not days, when the next CVE lands.
- Lock down CI — least-privilege tokens, no long-lived secrets, and separate the job that builds from the job that publishes.
- Vendor or mirror your most critical dependencies so a deleted or hijacked package upstream can’t break or breach you overnight.
Two of these carry most of the weight. Turning off arbitrary install scripts closes the single most common path to code execution, and using npm ci against a committed lockfile kills the silent-substitution attacks outright.
# Refuse arbitrary install scripts, and verify every package against the lockfile
npm config set ignore-scripts true
npm ci --no-audit # exact, hash-checked installs from package-lock.json
# Re-enable builds only for the packages you've actually vetted
npm rebuild esbuildDon’t let security eat the team
It’s possible to overcorrect. A review process so heavy that updates pile up for months is its own vulnerability — you end up running known-bad versions because patching has become painful. The teams that handle this well make the safe path the easy path: automated checks in CI, sane defaults, and human review reserved for the changes that actually warrant it.
Treat your dependency graph like production infrastructure, because that’s exactly what it is. You don’t need to read every package. You do need to know what you depend on, control what runs at install time, and be able to answer “are we exposed? ” before the people attacking you can.
Enjoyed this?
Get the next deep dive in your inbox. No spam — just the stories worth reading.
Subscribe to the newsletter