35+
Major companies including Apple, Microsoft, PayPal, and Netflix found vulnerable by Alex Birsan in 2021
3
Package ecosystems most commonly targeted: npm (JavaScript), pip (Python), RubyGems (Ruby)
4
Controls that prevent dependency confusion: scoped packages, registry pinning, version pinning, and lockfile verification
$30K
Bug bounty paid by one company to Alex Birsan for a single dependency confusion proof-of-concept

SponsoredRetool

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.

Start building for free today

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.

Free daily briefing

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

  1. Alex Birsan: Dependency Confusion Research
  2. GitHub: Dependency Confusion Prevention
  3. CISA: Defending Against Software Supply Chain Attacks

Free resources

25
Free download

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.

No spam. Unsubscribe anytime.

Free download

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.

No spam. Unsubscribe anytime.

Free newsletter

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.

Eric Bang
Author

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.

Black Hat Giveaway

Win a $2,495 Black Hat pass.

Full-access to Black Hat USA 2026 in Las Vegas. Subscribe free to enter.

Joins Decryption Digest daily briefing. Unsubscribe anytime.

Giveaway: Black Hat USA 2026 Full-Access Pass ($2,495 value)

Details →
Daily Briefing

Subscribe to enter the giveaway

Every subscriber is automatically entered. You also get daily threat intel every morning: zero-days, ransomware, and nation-state campaigns. Free. No spam.

Already subscribed? You're already entered.

Giveaway

Win a $2,495 Black Hat USA 2026 pass.