⚙ Application Security

Dependency Confusion and Package Security

Dependency confusion, also called a substitution attack, is a software supply chain technique in which an attacker tricks a package manager into downloading a malicious public package instead of the private, internal one a developer intended. It exploits how build tools resolve package names across multiple sources, and it can lead to remote code execution inside a company's build infrastructure without any traditional break-in. Understanding it is important for anyone managing internal packages, because the fix is a configuration discipline rather than a single patch.

How Package Resolution Creates the Risk

Modern software depends on package managers such as npm, pip, and their equivalents to fetch libraries by name and version. Organizations commonly build private packages — internal utilities named something like acme-internal-auth — and host them on a private registry while still pulling open-source dependencies from a public registry.

The vulnerability arises from how many package managers are configured to search both sources for a given name. When a package manager is told to install acme-internal-auth, it may query the public registry as well as the private one. If it finds the name in more than one place, a naive resolution strategy often picks the highest version number, regardless of which registry it came from. That single design choice is the entire foundation of the attack.

How the Attack Works

Dependency confusion typically unfolds in a few steps:

  1. Reconnaissance. The attacker learns the names of an organization's private packages. These names leak more often than teams expect — in public source repositories, in committed lockfiles or manifest files, in error messages, or inside published front-end bundles that reference internal module names.
  2. Registration. The attacker publishes a package with the exact same name on a public registry, where no one has claimed it. Public registries generally let anyone claim an unused name.
  3. Version inflation. The attacker sets a very high version number, such as 99.0.0, so any "highest version wins" resolution prefers the malicious package over the legitimate internal one.
  4. Execution. The next time a build resolves that name and reaches the public registry, it downloads the attacker's package. Many ecosystems run install-time scripts automatically, so the malicious code executes immediately inside the build environment, often with access to source code, credentials, and internal networks.

Because the package name matches perfectly, this differs from typosquatting, which relies on a developer mistyping a name. Dependency confusion needs no mistake at all; the tooling substitutes the package on its own.

Why Build Systems Are High-Value Targets

The damage is severe because the malicious code runs in CI/CD and developer environments, which are among the most privileged and trusted parts of an organization. These systems typically hold cloud credentials, signing keys, and access to production deployment. Code executing there can exfiltrate secrets, tamper with build outputs, or pivot deeper into internal infrastructure. Dependency confusion is thus a prime example of the broader risks covered in software supply chain security.

Defenses and Prevention

No single toggle eliminates dependency confusion; a layered configuration strategy does. The following controls reinforce one another.

Claim and Scope Your Namespaces

  • Register your internal names publicly. Defensively publish placeholder packages, or reserve an organization scope or namespace (for example @acme/), on the public registry so an attacker cannot claim your names at all.
  • Use scoped packages tied to your organization so internal names are not globally guessable and cannot collide with arbitrary public packages.

Control Where Packages Come From

  • Do not mix sources for the same name. Configure tooling so internal packages are fetched only from the internal registry, removing the ambiguity the attack depends on.
  • Use a single trusted proxy. Route all installs through one artifact repository that mirrors approved public packages and serves private ones, giving a single policy point instead of many client configurations.
  • Explicitly bind clients to registries. Point clients at your controlled source rather than the default public one.

A common npm mitigation binds a scope to your registry explicitly:

# .npmrc
@acme:registry=https://registry.internal.acme.example
always-auth=true

Verify and Pin Dependencies

  • Pin exact versions and hashes in a committed lockfile so a build cannot silently jump to an unexpected 99.0.0 from an unexpected source.
  • Verify integrity using the cryptographic hashes recorded in the lockfile, which fail the build if the downloaded artifact does not match.
  • Disable automatic install scripts where feasible, or run installs in a sandbox, to blunt the impact if a substitution slips through.

Combine with Broader Controls

Reproducible, verifiable builds strengthen these defenses further. Practices from reproducible builds let you confirm that the code you audited is the code you shipped, closing the gap between what a registry served and what actually ran.

Key Takeaways

  • Dependency confusion exploits package managers that search both public and private registries and prefer the highest version, causing a malicious public package to replace an intended private one.
  • The attacker needs only the exact internal package name and a high version number, so it requires no developer mistake, unlike typosquatting.
  • The impact is severe because malicious code runs inside privileged build and developer environments with access to secrets and networks.
  • Prevent it by reserving namespaces, binding internal names to a single trusted registry or proxy, and never mixing sources for the same package name.
  • Reinforce with pinned, hash-verified lockfiles, disabled install scripts, and reproducible builds as part of overall supply chain security.
dependency-confusionpackage-securitysupply-chaindependenciesappsec

Frequently asked questions

What is dependency confusion?

Dependency confusion, also called a substitution attack, is a software supply chain technique in which an attacker tricks a package manager into downloading a malicious public package instead of the private, internal one a developer intended. It exploits how build tools resolve package names across multiple sources, and it can lead to remote code execution inside a company's build infrastructure without any traditional break-in. The fix is a configuration discipline rather than a single patch.

How does a dependency confusion attack work?

The attacker first learns the name of an organization's private package, which often leaks in public source repositories, committed lockfiles, error messages, or front-end bundles. They then publish a package with the exact same name on a public registry and set a very high version number, such as 99.0.0. When a build resolves that name and queries both registries, a highest-version-wins strategy prefers the malicious public package, and because many ecosystems run install-time scripts automatically, the code executes immediately inside the build environment.

What is the difference between dependency confusion and typosquatting?

Typosquatting relies on a developer mistyping a package name, so the attacker publishes malware under a name close to a popular one, such as reqeusts instead of requests. Dependency confusion needs no mistake at all: the package name matches the intended private one perfectly, and the tooling substitutes the malicious public package on its own because of how it resolves names across public and private registries.

Why are build systems high-value targets for dependency confusion?

The malicious code runs inside CI/CD and developer environments, which are among the most privileged and trusted parts of an organization. These systems typically hold cloud credentials, signing keys, and access to production deployment. Code executing there can exfiltrate secrets, tamper with build outputs, or pivot deeper into internal infrastructure, which is what makes the impact so severe.

How do I prevent dependency confusion?

Use a layered configuration strategy. Reserve your internal package names, or an organization scope, on the public registry so an attacker cannot claim them, and never mix sources for the same name by binding internal packages to a single trusted registry or proxy. Reinforce this with pinned, hash-verified lockfiles so a build cannot silently jump to an unexpected version, and disable automatic install scripts or run installs in a sandbox to blunt the impact if a substitution slips through.

What are scoped packages and how do they help?

A scoped package ties an internal package name to your organization, for example an @acme namespace, so internal names are not globally guessable and cannot collide with arbitrary public packages. Binding that scope to your internal registry in client configuration ensures those names are fetched only from the controlled source. This removes the resolution ambiguity that dependency confusion depends on.

Try it hands-on

K0G is an open toolkit of browser-based security utilities — hashing, encoding, JWT, certificates, crypto and more, all running locally in your browser.

Explore the tools →