Dependency Confusion: Detection and risk mitigation

Dependency confusion attacks the supply chain security to deliver malicious code into the development environment. Developers use package manager systems to download packages (dependencies) and build projects. Usually, developers add some packages to the build file and initiate installing or downloading those packages. Downloading happens from public repositories, in common, most familiar: npmjs.com and PyPI.org. Anyone can register and publish their packages.

Sometimes, companies develop their packages and host them in internal repositories. On the market, you can find a lot of different engines for package hosting, like GitHub Packages, Nexus, Artifactory, etc.

Using internal packages opens new attack vectors, including Dependency Confusion. The attacker looks for internal package names, crafts malicious packages, and uploads them to the public repository. What could happen next? If the developer doesn’t configure their environment correctly, the system could download the package from the public repository instead of the internal one

That’s dependency confusion: uploading the package to the public repository, hoping the system will install it instead of the private one.

How could you find affected packages?

There are different ways to find these packages, depending on whether you are an employee or a bug bounty hunter. To handle this, I’m following the techniques used to collect internal package names.

Analyzing internal repositories

Accessing to internal infrastructure makes your process easier. You can find internal package names by analyzing internal package repositories. Here is what it looks like:

  • Collect information on what places are used as a source of packages.
  • Collect names of package repositories in it.
  • Collect unique package name versions.
  • Validate each name through the API of the public repositories. If the package name doesn’t exist on the public repository, it could be an internal package name.

There is a critical notice about false-negative results: internal package names could match publicly available packages. It’s a supply-chain security problem anyway, but it is harder to find in this way.

I found only one automated solution for this and tried it in the field for the artifactory repository.

Analyzing build files

You could collect all the available build files, like package.json those in your project, to find internal package names. You could use open-source solutions like the confused tool to automate the process. It supports analyzing different build files: requirements.txt, package.json, pom.xml, Gemfile.lock but it wasn’t updated for years.

This approach should also work for bug hunting because you could collect information about used dependencies in open-source repositories.

Analyzing published web applications

Sometimes, public web applications expose their package.json files. With it, you can find and verify a detailed list of used dependencies to find affected packages.

There are tools for automatic detection:

Mitigations for different build systems

Okay, you have a detailed list of internal package names. What’s next? It depends on your build system. This article describes mitigations for the node.js, Python, PHP, and Java ecosystems.

Dependency Confusion in Node.js: npm

NPM has a mechanism called scopes. You could use a scope to reserve any package names for the organization. It looks like @organization/package-name the organization is the scope name. After registering the nom organization, you can be sure that nobody can publish a package under this scope, closing the dependency confusion risks.

  • If the package name doesn’t include a scope, then just migrate to the registered one.
  • If internal package names have a scope (start with @) then you should register this organization on npmjs.org to make sure nobody can register it instead of you to deliver malicious code.
  • If someone has already squatted the npm scope, you should migrate to a scope you can control as soon as possible because there is a real risk of dependency confusion.

Another essential part is a configuration for the development environment and CI servers. Make sure your .npmrc is correctly configured to download scope packages from your internal resources.

Dependency Confusion in Python: pip, pipenv, poetry, uv

Unfortunately, Python package managers don’t have scopes, so someone could register all the package names to make the dependency confusion happen. There is only one call to action: publish the stub package to the public repository. With this action, you will act proactively by taking names to prevent name squatting.

What if someone has already taken the name? Migrate to a different Python package name because it creates dependency confusion.

If you’re using poetry, you could configure the download URL for each internal dependency to ensure poetry will download the package from the internal source. Another useful option is to use modern tools like poetry, uv, and pipenv because they generate lock files for your dependencies. This will lock dependencies and partially close dependency confusion risk for Python packages.

One fun fact: if someone deletes a package from PyPI, someone else could squat it. It could be a problem because attackers could find those names to attack to attack users that depend on removed dependency.

Read about this attack: Revival Hijacking: How Deleted PyPI Packages Become Threats

Dependency confusion in PHP: Composer

Packagist has mechanisms to prevent dependency confusion in the PHP ecosystem. It is similar to npm scopes but works differently. It calls vendor prefixes. The system reserves vendor prefixes for your account if you publish the public package once; it happens automatically. Vendor prefixes look like: protsenko/package-name, where protsenko is the vendor prefix.

Mitigation is similar to the node.js ecosystem: use vendor prefixes and migrate to different ones if you can’t register it yourself. Configure composer files correctly, as described in the original article. With these simple actions, you will close the dependency confusion problem for PHP packages.

Dependency Confusion in .NET: NuGet

NuGet is just as susceptible to dependency confusion as npm or pip, and this is a problem for supply-chain security. If your build restores from both nuget.org and an internal feed, a malicious actor can publish a higher-versioned twin of one of your private packages to nuget.org. The CLI will happily pick the public imposter and pull it straight into your build.

How to close the gap:

  1. Enable Package Source Mapping (NuGet 6+). Map each package ID or prefix to a single trusted feed. Any package that doesn’t fit the map fails the build, blocking “split-brain” restores outright.
  2. Reserve your ID prefix on nuget.org. NuGet Gallery lets you claim a namespace such as MyCompany.*. Once reserved, only accounts you approve can publish anything under that prefix, preventing public name-squatting even if you never push private code to nuget.org.
  3. Mirror public packages into a single private upstream. Artifacts services (Nexus, Artifactory) can proxy nuget.org. Point all restores to that proxy so the build never contacts multiple feeds.
  4. Add Trusted Signers and lock files. Require author-signed or repository-signed packages and commit packages.lock.json. If an attacker tampers with content or swaps a dependency, the signature or hash check will fail, and the build will break instead of shipping malware.

Finally, lock down package sources: Put a repo-local nuget.config that begins with:

<configuration>
  <packageSources>
    <clear/>
    <add key="MyPrivate" value="https://nuget.mycorp.local/v3/index.json"/>
  </packageSources>
</configuration>

The leading <clear/> wipes any machine-wide or user-wide feeds, guaranteeing every restore comes only from the feed you control

Microsoft’s own Secure software supply chain guidelines for NuGet walk through these same steps—ID-prefix reservation, source mapping, feed hardening, and signed packages—so you can treat the checklist above as a condensed field manual.

Dependency Confusion in Java/Kotlin: Maven

Dependency Confusion in the Maven ecosystem is rare because Maven Central relies on strict verification before registering new group IDs. Verifications are based on the DNS that the user controls. For example, as an owner of the domain protsenko.dev. Only I could register the package with group ID: dev.protsenko.

But this applies mainly to Maven Central. Other public repositories could have less strict verifications. In that case, an attacker could squat the package name, but to exploit this attack, you should declare that repository in a build file like pom.xml. If you don’t want to deal with dependency confusion in maven ecosystem – use only verified or controlled repositories.

Verify that your internal packages lie in the group ID with the domain you can control. In other cases, an attacker could register the domain name with your package group ID and publish packages to the public repository.

One more security issue related to dependency confusion was hidden in the Maven ecosystem, called Maven Gate. This problem exploits abandoned packages, expired domain names, and multiple repository declarations, allowing an attacker to register a domain and publish a package to a repository where registration is available for a given group ID.

The future of Dependency Confusion articles

I’m not proficient at ending articles. There could be some buzzwords about the importance of mitigating risks, but I think you know it well.

That’s all for now. If you’re interested in software supply chain security or attacks, follow me on LinkedIn to learn about new articles. If you like this article, read my related article: Using SBOMs to detect possible Dependency Confusion

Avatar photo
Dmitry Protsenko

Senior Software Engineer
Specialized on Java / Kotlin and CyberSecurity
Author of this blog

Articles: 34