What Is a Dependency Confusion Attack and How Do You Prevent It

Retool's new app builder is where AI-generated code ships safely
Building apps with AI is easy. Getting them to production safely is another story.
In 2021, security researcher Alex Birsan found internal package names from Apple, Microsoft, Netflix, PayPal, and dozens of other companies by examining JavaScript files and build artifacts. He then published packages with those exact names to the public npm registry, set the version number higher than the private package's version, and watched as automated build systems at major corporations downloaded and executed his code instead of the internal package.
He reported all findings responsibly and collected over $130,000 in bug bounties. The attack technique has since been replicated by actual threat actors in real campaigns.
How the Attack Works, Step by Step
Step 1: Attacker discovers internal package names.
Internal package names leak through multiple vectors: JavaScript bundles visible in browser DevTools (webpack bundles often include require('internal-analytics-client') in minified form), error messages in public-facing applications, Docker images pushed to public registries with package.json or requirements.txt included, and job postings that mention specific internal tools by name.
Step 2: Attacker publishes a malicious package with the same name.
The attacker creates a package named internal-analytics-client (or whatever internal name was discovered) and publishes it to the public npm registry. Critical detail: the attacker sets the version number higher than whatever the internal package's version is: typically something like 9999.0.0.
Step 3: Package manager resolution selects the public package.
When the vulnerable build system runs npm install, it checks both the private registry and the public npm registry. In the default configuration, npm prefers the highest version number regardless of which registry it comes from. Version 9999.0.0 from the public registry beats version 1.2.3 from the private registry.
Step 4: Malicious code executes during installation.
The malicious package includes a postinstall script: code that runs automatically when npm installs the package. This script exfiltrates environment variables, internal hostnames, and any secrets present in the build environment to the attacker's collection endpoint.
Step 5: Attacker receives build environment data. The exfiltration often includes: environment variables (which frequently contain cloud credentials, API keys, and database URLs), the internal hostname (for targeting follow-up attacks), and the user running the build.
Which Package Managers Are Vulnerable
The vulnerability exists in any package manager that consults both a private and a public registry and uses version number or any other criteria that could favor the public package.
npm (JavaScript/Node.js):
Vulnerable in the default configuration when a .npmrc file specifies a private registry alongside the public npm registry without scope locking. The --registry flag does not fully protect against confusion when both registries contain packages with the same name.
pip (Python):
Vulnerable when using --extra-index-url to add a private PyPI index alongside the public PyPI. When both indexes contain a package with the same name, pip by default installs the highest version available across all indexes.
RubyGems:
Vulnerable when a Gemfile specifies both a private source and the public rubygems.org. Bundler resolves across all sources.
Maven (Java) and NuGet (.NET): Vulnerable in multi-repository configurations without explicit repository pinning per artifact.
Not vulnerable by default: Package managers that use explicit repository pinning (each dependency specifies which registry to pull from) are not vulnerable. The vulnerability requires a configuration that allows a package manager to choose between registries for the same package name.
Briefings like this, every morning before 9am.
Threat intel, active CVEs, and campaign alerts, distilled for practitioners. 50,000+ subscribers. No noise.
Four Controls That Prevent Dependency Confusion
Control 1: Use scoped package names for all internal packages (npm)
In npm, scoped packages have names prefixed with @yourorg/: for example @yourcompany/analytics-client. When your .npmrc maps the @yourcompany scope to your private registry:
@yourcompany:registry=https://your-private-registry.example.com
npm will always resolve @yourcompany/* packages from the private registry only. An attacker cannot register a scoped package under your organization name on the public npm registry without controlling that organization's account.
Control 2: Pin your private registry and disable public fallback
For pip, use --index-url (exclusive private registry) instead of --extra-index-url (adds public as fallback):
# Vulnerable:
pip install -r requirements.txt --extra-index-url https://private.pypi.example.com/simple/
# Safe:
pip install -r requirements.txt --index-url https://private.pypi.example.com/simple/
For npm, if you need to use a private registry for all packages, set it as the default and route only public packages through npm's proxy feature (Verdaccio, Nexus, Artifactory all support proxying public registries while prioritizing private packages).
Control 3: Use a unified private registry that proxies public packages
Tools like JFrog Artifactory, Sonatype Nexus, and self-hosted Verdaccio act as a single registry endpoint for developers. When a package is requested, the private registry first checks its private package store, then proxies the request to the public registry if not found. This eliminates the multi-registry resolution problem: your build system only talks to one registry.
The critical configuration: ensure the private registry is configured to prefer private packages over proxied public packages when names conflict.
Control 4: Verify lockfiles in CI/CD and pin to verified hashes
Commit your package-lock.json, yarn.lock, Pipfile.lock, or Gemfile.lock and configure CI/CD to install using the lockfile rather than resolving dependencies fresh:
# npm: use lockfile, fail if it would change
npm ci
# pip: install exact versions from lockfile
pip install -r requirements.txt --require-hashes
npm ci will fail if the lockfile would change during installation: including if a dependency confusion substitution would install a different package. Combined with lockfile integrity verification (compare the lockfile hash before and after install), this provides a detection layer for substitution attacks.
Detection: Monitoring for Dependency Confusion Attempts
If your organization publishes internal package names in any public-facing artifact, you are likely already being probed. Monitor for dependency confusion attempts:
Claim your internal package names on public registries: Register placeholder packages on npm, PyPI, and RubyGems with your internal package names before an attacker does. Set the description to 'Internal [company] package: do not install' and include a contact email. This eliminates the attack surface and alerts you if someone attempts to publish a higher-versioned package under the same name.
Monitor for outbound DNS from build infrastructure:
Dependency confusion postinstall scripts exfiltrate data by making outbound HTTP or DNS requests. Build systems should have network egress restrictions: if your CI/CD runners can only reach your approved package registry and package download CDNs, exfiltration attempts will fail. Alert on any outbound connection from CI/CD to an unexpected destination.
Watch for unexpected postinstall scripts in new packages:
If your dependency management tooling supports it, audit new or updated dependencies for postinstall or preinstall scripts before allowing them into your environment. Many enterprise artifact management tools (Artifactory, Nexus) can block packages containing lifecycle scripts until they are manually reviewed.
The bottom line
Dependency confusion exploits multi-registry resolution order: when your private package and a malicious public package share a name, the attacker wins by setting a higher version number. Prevent it with scoped package names for all internal npm packages, exclusive registry configuration (not extra-index-url) for pip and other package managers, a unified private registry that proxies public packages to eliminate multi-registry resolution, and lockfile CI enforcement that detects substitution attempts. Claim your internal package names on public registries now: before an attacker does.
Frequently asked questions
What is a dependency confusion attack?
A dependency confusion attack exploits package manager resolution order by publishing a higher-versioned malicious package to a public registry with the same name as a company's internal private package. When the build system resolves dependencies, it picks the higher version from the public registry, downloading and executing the attacker's code instead of the legitimate internal package.
How do I prevent dependency confusion in npm?
Use scoped package names (e.g., @yourcompany/package-name) for all internal npm packages and map your company scope to your private registry in .npmrc. This prevents the public npm registry from resolving @yourcompany/* packages at all. Additionally, use a unified private registry that proxies public packages to eliminate multi-registry resolution.
Which package managers are vulnerable to dependency confusion attacks?
All major package managers have been demonstrated vulnerable: npm (Node.js), pip (Python), RubyGems, Maven (Java), NuGet (.NET), and Go modules. The attack surface exists wherever a build system resolves packages from multiple sources and the attacker can publish to a public registry with a higher version number than the private package. Alex Birsan's 2021 research demonstrated successful dependency confusion against Apple, Microsoft, Netflix, Uber, PayPal, and Tesla.
What is the difference between dependency confusion and typosquatting?
Dependency confusion exploits the package manager's resolution order by publishing a public package with the same name as an internal package at a higher version. The attacker does not need to trick anyone into installing the wrong name: the build system does it automatically. Typosquatting creates a package with a name visually similar to a popular legitimate package (e.g., 'lodahs' instead of 'lodash') hoping a developer will type the wrong name. Dependency confusion is more dangerous because it requires no user error: the attack is invisible to the developer.
How do I detect if my environment has been hit by a dependency confusion attack?
Audit your build logs for unexpected packages from public registries that share names with internal packages. Compare the installed package version against your private registry's version: if the public version is higher, you may have been hit. Check the package's install scripts (preinstall, postinstall) for network calls, data exfiltration code, or system command execution. Legitimate internal packages should not have install scripts that make outbound network requests. Also check your npm lockfile: unexpected registry URLs for internal package names are a strong indicator.
What is the difference between dependency confusion and typosquatting, and do the same defenses work for both?
Dependency confusion exploits the name resolution logic of package managers that check public registries before private ones: if your internal package 'company-utils' also exists on npmjs.com with a higher version, the package manager installs the public version. Typosquatting places a malicious package with a name similar to a popular legitimate package ('reqeust' instead of 'request') to catch developers who mistype. The attacks differ in targeting: dependency confusion targets organizations with private package ecosystems (the attacker needs to discover your internal package names, often from job postings or leaked package.json files). Typosquatting targets individual developers who make typos. The defenses differ substantially. Against dependency confusion: scope all internal packages under a private namespace (@company/utils), configure npm to use your private registry for that scope, and enable Artifactory or Nexus virtual repositories that prevent public registry lookups for namespaced packages. Against typosquatting: validate package names carefully, use package lock files to pin exact versions and hashes, and use tools like Socket Security or Phylum that analyze new dependencies for malicious behavior before they are installed.
Sources & references
Free resources
Critical CVE Reference Card 2025–2026
25 actively exploited vulnerabilities with CVSS scores, exploit status, and patch availability. Print it, pin it, share it with your SOC team.
Ransomware Incident Response Playbook
Step-by-step 24-hour IR checklist covering detection, containment, eradication, and recovery. Built for SOC teams, IR leads, and CISOs.
Get threat intel before your inbox does.
50,000+ security professionals read Decryption Digest for early warnings on zero-days, ransomware, and nation-state campaigns. Free, daily, no spam.
Unsubscribe anytime. We never sell your data.

Founder & Cybersecurity Evangelist, Decryption Digest
Cybersecurity professional with expertise in threat intelligence, vulnerability research, and enterprise security. Covers zero-days, ransomware, and nation-state operations for 50,000+ security professionals every morning.
Win a $2,495 Black Hat pass.
Full-access to Black Hat USA 2026 in Las Vegas. Subscribe free to enter.
