Supply Chain Attacks

Dependency Confusion Attacks Explained

Alex Birsan's research showed how internal package names can be exploited to inject malicious code into corporate build systems. Here's how the attack works and how to defend against it.

Bob
DevSecOps Engineer
6 min read

$130,000 in Bug Bounties from a Single Idea

In February 2021, security researcher Alex Birsan published research that earned him over $130,000 in bug bounties from Microsoft, Apple, and PayPal. The attack was elegant in its simplicity: exploit the way package managers resolve dependencies when both public and private registries exist.

He called it dependency confusion. The concept is straightforward. If a company uses internal packages with names like company-utils or internal-auth, an attacker can publish packages with those exact names on public registries like npm, PyPI, or RubyGems. When the company's build system resolves dependencies, it may prefer the public version over the private one — especially if the public version has a higher version number.

The result: attacker-controlled code runs inside corporate build pipelines.

How Package Managers Get Confused

The vulnerability exists because of how package managers handle registry resolution. The specifics vary by ecosystem, but the pattern is consistent.

npm

If a .npmrc file specifies a private registry but doesn't scope packages correctly, npm may fall back to the public registry for packages it can't find privately. Even worse, if a package exists in both registries, npm may prefer the version with the higher semver number — regardless of which registry hosts it.

PyPI

Python's pip is even more straightforward to exploit. If a company uses --extra-index-url to point to a private PyPI server, pip checks both the public PyPI and the private index. If the public index has a higher version number, pip installs the public version. There's no warning.

Other Ecosystems

The same pattern affects RubyGems, NuGet, Maven, and others. Any package manager that supports both public and private registries without strict resolution controls is potentially vulnerable.

Birsan's Research

Birsan's approach was methodical. He looked for internal package names in publicly accessible files — JavaScript source maps, package.json files exposed in error pages, GitHub repositories, and internal documentation that had been inadvertently published.

Once he identified internal package names, he published packages with those names on public registries with extremely high version numbers (e.g., 99.0.0). The packages contained harmless callbacks to his server, proving that code execution occurred without actually causing damage.

The results were striking:

  • Microsoft — Multiple internal build systems installed his packages
  • Apple — Build systems pulled his packages over internal versions
  • PayPal — Same behavior across multiple internal projects
  • Shopify, Netflix, Yelp, Uber — All confirmed vulnerable

These are companies with massive security teams. The attack succeeded not because of poor security practices at any individual company, but because of fundamental design decisions in package management tools.

The Attack in Practice

Here's what a malicious dependency confusion attack looks like:

  1. Reconnaissance — Attacker identifies internal package names through leaked manifests, job postings mentioning internal tools, or social engineering
  2. Package Creation — Attacker publishes a package with the matching name on the public registry, with a high version number
  3. Payload — The package includes a preinstall or postinstall script that executes immediately when the package is installed
  4. Exfiltration — The script collects hostname, username, current directory, and network information, sending it to an attacker-controlled server
  5. Persistence — In a real attack (unlike Birsan's research), the payload could establish persistence, steal credentials, or modify source code

The preinstall script is particularly dangerous because it runs before any other part of the package is evaluated. On many systems, it runs with the permissions of the build process — which often has access to source code, secrets, and deployment credentials.

Defending Against Dependency Confusion

1. Use Scoped Packages (npm)

In npm, use organization-scoped packages (@company/package-name). Scoped packages always resolve to the configured registry for that scope, eliminating confusion between public and private registries.

2. Pin to Private Registries

Configure your package managers to use your private registry exclusively, with the private registry configured to proxy public packages. This way, all resolution goes through a single controlled point.

For pip, use --index-url (exclusive) instead of --extra-index-url (additive):

pip install --index-url https://private.registry.company.com/simple/

3. Claim Your Names

If you use internal package names, register them on public registries as placeholder packages. This prevents attackers from squatting on your names. It's a band-aid, but it's immediately effective.

4. Use Lockfiles and Checksums

Lockfiles pin exact versions and integrity hashes. If a dependency resolves to a different package than what the lockfile specifies, the install should fail. Always commit lockfiles and enforce integrity verification in CI.

5. Monitor Public Registries

Set up alerts for when packages matching your internal names appear on public registries. Several tools and services now offer this capability.

6. Restrict Install Scripts

Disable preinstall and postinstall scripts in your CI environment using --ignore-scripts for npm. Evaluate scripts before allowing them to run.

The Ongoing Threat

Birsan's research was responsible disclosure. But the technique he described is now well-known and actively exploited. In the months since his publication, security researchers have observed multiple malicious dependency confusion attempts in the wild. Some registries have added mitigations — npm now supports registry-level scoping, and PyPI has discussed namespace reservation — but the fundamental architecture of multi-registry resolution remains a risk in many configurations.

Every organization that uses private packages needs to audit their package manager configurations. The fix is usually straightforward — proper scoping, exclusive registry configuration, and lockfile enforcement. But many teams don't know they're vulnerable until an attacker demonstrates it.

How Safeguard.sh Helps

Safeguard.sh detects dependency confusion risks by analyzing your project manifests and comparing internal package names against public registries. If a package name used in your private registry also exists on npm, PyPI, or other public registries, the platform flags it as a potential confusion vector — before an attacker exploits it.

The platform also monitors public registries for newly published packages that match your internal naming patterns, providing early warning of potential squatting or confusion attacks. This proactive monitoring closes the gap between an attacker publishing a malicious package and your build system potentially installing it.

Beyond detection, Safeguard.sh validates your package manager configurations against best practices — checking for proper scoping, exclusive registry settings, lockfile integrity, and install script restrictions. These configuration checks run continuously, ensuring that a misconfigured .npmrc or pip.conf doesn't silently reintroduce dependency confusion risks into your build pipeline.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.