Supply-chain
-
Your Software Is Mostly Strangers' Code
Modern applications aren’t really written anymore. They’re assembled. Seventy to ninety percent of a typical proprietary codebase is open-source code pulled from public registries, NPM, PyPI, crates.io, maintained by thousands of people you’ve never met. Every
npm installis an act of implicit trust extended to strangers, and that trust model has quietly become the weakest link in most security architectures.Attackers figured this out a long time ago. Compromising one popular package gives you a blast radius that phishing campaigns can only dream about: CI pipelines, developer laptops, production workloads, client devices, all simultaneously. SolarWinds. The XZ Utils backdoor. The Shai-Hulud worm, which self-propagated through 170+ npm and PyPI packages by hijacking GitHub Actions OIDC tokens and quietly minted new publish credentials as it spread. The ByBit developer compromise. These aren’t outliers anymore. They’re the shape of the threat.
I want to dig into the mechanics of how this actually happens, and then look at the ecosystem most developers touch every day: NPM.
How Supply Chain Attacks Actually Work
The first thing to understand is that supply chain attacks aren’t really “vulnerabilities” in the classic sense. A buffer overflow is an accidental weakness. A malicious package is intentional code, written to steal credentials, drop a reverse shell, or exfiltrate environment variables the moment it lands on your machine. Traditional appsec tools were built to find the former. They are largely blind to the latter.
The attack patterns cluster into a few categories.
Typosquatting. Publish
axoisand wait for someone to fat-fingeraxios. Sounds trivial, but it works constantly because developers install packages at high velocity and rarely double-check spelling.Dependency confusion. If your company has an internal package called
corp-auth, an attacker publishes a public package with the same name and a higher version number. Many package managers default to “highest version wins,” and your build pulls the public one instead of your internal one.Maintainer hijacking. Compromise a real maintainer through phishing, credential stuffing, or a missing 2FA setup, and push a poisoned update to a package that already has millions of weekly downloads. The Axios compromise in March 2026 followed exactly this pattern. The XZ Utils backdoor was a slower variant. The attacker spent months building trust as a “helpful” co-maintainer before slipping a backdoor into the build.
The thing that makes all of this so effective is the automation downstream. Unpinned versions, auto-merging update bots, transitive dependencies five layers deep. Once a malicious version hits the registry, it propagates fast.
Why NPM Is the Highest-Stakes Ecosystem
NPM serves tens of billions of downloads a week. A typical JavaScript project today pulls in well over a thousand transitive dependencies. Ten years ago that number was in the dozens. The dependency graph is just structurally enormous, and it’s getting worse.
The specific architectural problem in NPM is the lifecycle script, specifically
postinstall. NPM lets package authors define scripts inpackage.jsonthat run automatically when the package is installed. This was designed for legitimate reasons: compiling native bindings, configuring environments. But it also means arbitrary shell commands execute on your machine the moment you typenpm install. No code review. No second thought. Just immediate execution as your user.There are a few practical mitigations, and they’re worth knowing whether you’re a solo developer or running platform security at a large org.
Disable lifecycle scripts. Either pass
--ignore-scriptsad hoc, or set it globally:npm config set ignore-scripts trueThis breaks some legitimate packages (esbuild, bcrypt, anything compiling native code). To manage that, tools like
can-i-ignore-scriptsscan yournode_modulesand generate an allowlist of packages that genuinely need scripts to run. Frameworks like@lavamoat/allow-scriptsformalize this with a deterministic config you can check into the repo.Use
npm ciin CI, notnpm install. This is non-negotiable for production builds.npm installwill happily resolve newer minor versions inside your semver ranges and rewritepackage-lock.json.npm cirefuses to do that. If the lockfile doesn’t match exactly, the install fails. That’s the behavior you want when the question is “did anything change that I didn’t approve.”Consider switching to pnpm. pnpm 10+ has been quietly building some of the best structural defenses in the ecosystem. Postinstall scripts are off by default and require an explicit
allowBuildslist.blockExoticSubdepsprevents transitive deps from resolving via random Git URLs or tarballs.The killer feature, though, is
minimumReleaseAge. As of pnpm v11 (May 2026), the default is 1440 minutes, so pnpm simply refuses to resolve any package version less than 24 hours old. Most malicious packages get pulled from the registry within hours of being detected. A 24-hour cooldown turns the community into your early warning system, with no behavioral analysis or commercial tooling needed.That last one is the single highest-leverage change you can make as an individual developer. It costs nothing, it doesn’t break your workflow, and it neutralizes most day-zero registry malware before it ever reaches you.
Next post I’ll dig into the Python and Rust sides of this. pip’s
setup.pyexecution problem, Rust’sbuild.rsissue, and the surprisingly mature auditing toolchain the Rust community has built aroundcargo-audit,cargo-deny, andcargo-vet.Sources
- Securing Package Managers: Why NPM, PyPI, and Cargo Are High-Value Targets
- Defending Against NPM Supply Chain Attacks: A Practical Guide
- NPM Ignore Scripts Best Practices
- Mitigating supply chain attacks
- Get safe and remain productive with can-i-ignore-scripts
- The Landscape of Malicious Open Source Packages: 2025 Mid-Year Threat Report
- The Evolving Software Supply Chain Attack Surface
- Introducing OpenSSF’s Malicious Packages Repository
I’d appreciate a follow. You can subscribe with your email below. The emails go out once a week, or you can find me on Mastodon at @[email protected].
/ DevOps / security / javascript / Npm / Supply-chain