The npm ecosystem's openness is exactly what makes it powerful — and exactly what makes it a recurring target for supply chain attacks that exploit trust in the dependency chain itself.
Compromised maintainer accounts
A legitimate, widely-used package can be compromised if an attacker gains access to the maintainer's npm account — often via credential stuffing or a phishing attack unrelated to the package's code quality. The attacker then publishes a malicious version under the real package's trusted name, and anyone who updates without pinning receives it automatically.
Defense: Pin exact dependency versions in your lockfile, and don't auto-update without review. Encourage (or require, for internal packages) maintainer 2FA.
Typosquatting
Attackers publish packages with names deliberately similar to popular ones (lodahs instead of lodash), betting on developer typos during manual installation.
Defense: Use autocomplete-aware tooling, review new dependencies before adding them, and consider a private registry proxy that can flag unusually-named new packages.
Malicious postinstall scripts
npm packages can define scripts that run automatically during installation — including postinstall — with the same permissions as whoever ran npm install. A malicious package can use this to exfiltrate environment variables, credentials, or SSH keys the moment it's installed, without the code ever needing to be imported or executed by the application itself.
Defense: Consider running npm install --ignore-scripts in CI where feasible, and review postinstall scripts for new or unfamiliar dependencies before allowing them.
Dependency confusion
Covered in more depth elsewhere, but worth noting here: an attacker publishing a public package matching an internal private package's name, exploiting registry resolution order.
Practical defenses across all of these
- Lockfiles, always committed and enforced in CI (
npm ci, notnpm install). - Software composition analysis scanning for known-malicious or known-vulnerable packages.
- Minimize dependency count — every dependency, and every transitive dependency it pulls in, is additional trust you're extending.
- Review before upgrading major/unfamiliar packages, rather than blindly accepting Dependabot-style auto-merge for everything.
Supply chain security in npm isn't about avoiding open source — it's about treating every dependency addition as extending trust to code you didn't write and haven't reviewed, and applying just enough friction to that decision.