The main types of dependency supply-chain attack
"Supply-chain attack" covers several quite different techniques that happen to share an outcome. Telling them apart is useful, because the defence that stops one does nothing against another.
Updated · Precursor Security
Typosquatting
The attacker publishes a package whose name is a plausible mistyping of a popular one - socktio for socket.io, fsextrra for fs-extra - and waits for someone to typo it, copy it from a bad tutorial, or accept an LLM suggestion that hallucinated the name.
How it reaches you: a human error at the moment of installation, which then gets committed to a lockfile and propagates to everyone on the team.
What helps: reading the name before installing; a private registry allowlist; lockfile review. Version pinning does not help - you pinned the wrong package.
Dependency confusion
Your organisation has a private package, say @company/auth-utils. An attacker publishes a package with that exact name on the public registry, usually with an absurdly high version number like 99.0.0. If any resolver consults the public registry before or alongside your private one, the public version wins on version precedence.
How it reaches you: a misconfigured registry setting, often in a CI environment rather than on a laptop.
What helps: scoping private packages and locking the scope to your internal registry; explicitly configuring registry precedence rather than relying on defaults; watching the public registry for your own internal package names.
Maintainer account takeover
The package is the real one. The attacker took over the maintainer's account - phished credentials, a stolen publish token, or a compromised CI pipeline - and published a malicious version under the legitimate name.
This is the hardest variant, because every name-based check passes. You installed exactly the package you intended, from exactly the right maintainer, at a version that satisfies exactly the range you declared.
How it reaches you: a routine upgrade. Often an automated one.
What helps: delaying automatic upgrades by a few days so the community finds it first; reviewing lockfile diffs; and checking installed versions against known-compromised releases after the fact, since prevention largely is not available here.
Self-propagating worms
A newer and more serious pattern: the payload searches the compromised machine for registry publish tokens and uses them to inject itself into that victim's packages, which then infect their consumers.
This turns a single developer compromise into exponential spread. It is also why "rotate your registry token first" is the top item in the incident-response guide - it is the step that stops you becoming a distribution node.
What helps: short-lived and scoped publish tokens; requiring 2FA or trusted publishing for releases; never leaving a long-lived publish token in a general-purpose CI environment.
Install-time versus import-time execution
Cutting across all of the above is when the code runs, which decides how much your application architecture protects you.
Install-time - an npm postinstall hook, a PyPI setup.py or build hook. Runs during npm install or pip install, before your application exists. Sandboxing your app is irrelevant; a failed or cancelled install still detonated it. This is the majority of what we track.
Import-time - the payload runs when your code imports the module. Requires the package to actually be reachable from your application, so a compromised dev-only dependency may never execute in production - though it will still have run on developer machines.
The practical consequence: for install-time payloads, the question is never "was this package in the code path". It is "did this ever get installed anywhere".
Common questions
- Which of these is most common?
- By raw package count, typosquatting - it is cheap and scales to dozens of packages per campaign. By impact, maintainer account takeover, because it reaches everyone who upgrades a package they already trust.
- Does this only affect npm?
- No. We track the same patterns across PyPI, RubyGems, crates.io, Packagist, NuGet, Go modules, Docker Hub, Open VSX, and GitHub Actions. npm sees the most volume, largely because of its size and the depth of typical transitive trees.
- Does a Software Bill of Materials prevent this?
- An SBOM records what you shipped; it does not judge whether any of it is safe. It is genuinely valuable after disclosure - it makes "are we affected" answerable in minutes rather than days - but it is an inventory, not a control.