What Is a Dependency Confusion Attack?
A dependency confusion attack publishes a malicious public package with the same name as an internal one, tricking installs into pulling the wrong code.
A dependency confusion attack exploits how package managers resolve names across multiple registries: an attacker publishes a malicious package to a public registry using the exact name of a company’s internal, private package, and gives it a higher version number. If the build tooling checks the public registry alongside (or instead of) the private one, it installs the attacker’s package — no compromised credentials or vulnerable code required, just a naming collision and a version bump.
Why the collision even happens
Most organizations run private packages internally — shared UI components, internal SDKs, deployment scripts — installed from a private registry, npm scope, or internal proxy alongside public packages from npm, PyPI, or RubyGems. Many package managers were built to check several sources and resolve by whichever one satisfies the request, sometimes preferring the highest version number across all of them rather than trusting a single source outright. An internal package named analytics-utils at version 2.1.0 is invisible to the outside world — until someone publishes a public package by the same name at version 9.0.0. A resolver configured to trust version numbers over source will happily fetch the impostor.
The attacker doesn’t need to guess the name. Internal package names leak constantly: they show up in public GitHub repositories, error stack traces, job postings describing a company’s stack, open source projects that reference internal tooling, or a package.json/requirements.txt accidentally committed to a public repo.
How it differs from typosquatting
Typosquatting relies on a human mistake — a developer fat-fingering lodash as lodahs and installing the wrong package. Dependency confusion needs no typo at all; it targets automated resolution logic with the correct name, exploiting how the tool picks between registries rather than how a person types.
| Typosquatting | Dependency confusion | |
|---|---|---|
| Trigger | Human types a misspelled name | Resolver picks the wrong registry |
| Target name | Similar to a popular public package | Identical to a private, internal package |
| Requires a typo | Yes | No |
| Primary defense | Careful installs, name-similarity scanning | Registry scoping and namespace claims |
Mitigations that actually close the gap
- Scope internal packages. Publishing under a reserved scope (
@yourcompany/analytics-utils) removes the ambiguity — public registries can’t publish into a scope they don’t control, so there’s no name to collide with. - Pin the registry per scope, explicitly. Configuration like npm’s
.npmrccan map a scope to a specific registry rather than letting the tool search multiple sources and pick a winner by version number. Set it at the project and CI level, not just on developer machines. - Claim the name publicly, even if you never publish to it. Registering a placeholder package under the internal name on the public registry blocks an attacker from taking it, at the cost of a little upkeep.
- Audit what your CI actually resolves. A one-time review of exactly which registry each dependency in a lockfile came from catches a confusion attempt before it’s exploited, and is a natural extension of the checks in a software supply chain security program — an SBOM that records package origin, not just name and version, makes this auditable going forward.
- Avoid leaking internal names unnecessarily. Treat package names in public repos, job listings, and conference talks the same way you’d treat other internal implementation details.
Package managers aren’t all equally exposed
The specifics vary by ecosystem — npm, pnpm, and Yarn each handle scoped registries slightly differently, and some ecosystems resolve by highest version across all configured sources by default, which is the exact behavior that makes this attack work. Python’s pip and Ruby’s Bundler have both shipped their own variants of the same underlying problem, because the pattern isn’t specific to JavaScript — it’s specific to any tool that treats “highest version wins” as a safe default across multiple sources it doesn’t equally trust. Whatever the tool, the fix is the same in spirit: stop letting a bare package name be ambiguous about where it should come from.
What makes this attack class distinctive
Most supply chain attacks require the attacker to get something into a dependency you already trust — compromising a maintainer’s account, sneaking a malicious commit past review, or exploiting a known vulnerability that hasn’t been patched yet. Dependency confusion skips all of that. The attacker never touches your actual dependency, your CI pipeline’s credentials, or any code your team wrote. They publish something new, under a name that was never theirs to begin with, and wait for a build system somewhere to misresolve it. That’s also what makes it comparatively cheap to defend against once you know it’s a category of risk: there’s no patch to apply and no upstream maintainer to coordinate with, just a naming and configuration gap that’s entirely within your own control to close.
The takeaway
Dependency confusion doesn’t need a leaked credential or a vulnerable dependency — it needs a name your build tooling can’t tell apart from something malicious, and a resolver willing to pick the higher version number over the trusted source. Scoping internal packages, pinning registries per scope, and treating package origin as something worth auditing closes the gap without requiring any change to how your team actually writes code.
Keep reading
Chisato · · 4 min read What Is Typosquatting? The Package Ecosystem's Silent Trap
Typosquatting publishes malicious packages under names that look like popular ones, hoping developers mistype an install command. How it works.
Chisato · · 5 min read What Is a Bug Bounty Program?
A bug bounty program pays independent researchers for responsibly reporting security vulnerabilities before attackers find and exploit them.
Takina · · 7 min read Rust Adopts LLM Policy: What's Allowed for AI Code
Five rust-lang/rust teams ratified an LLM policy: models can analyze and review, but not author contributions. Here's what's permitted, banned, and why.