Skip to main content

Free 30-min security demo Book Now

Academy Dependency Confusion
Advanced · 20 min

Dependency Confusion

Learn how publishing higher-version public packages with internal names tricks package managers into installing malicious code.

1 How Dependency Confusion Attacks Work

Dependency confusion (also called namespace confusion) exploits how package managers resolve packages when both public and private registries are configured.

The attack scenario:

  1. Company uses an internal npm package: @company/utils (private, version 1.0.0)
  2. Attacker discovers the internal package name (from job listings, error messages, leaked configs)
  3. Attacker publishes @company/utils to the public npm registry at version 9.0.0
  4. When developers run npm install, npm checks the public registry first
  5. Version 9.0.0 > 1.0.0, so npm installs the ATTACKER'S version!

This vulnerability was demonstrated by Alex Birsan in 2021, who successfully compromised internal packages at Apple, Microsoft, PayPal, Tesla, and 30+ other companies.

Discovery methods:

  • package.json files in public repos or CDN bundles
  • Error messages revealing internal package names
  • Job postings mentioning internal tool names

2 Private Registry Configuration and Namespace Scoping

Prevent dependency confusion with explicit registry configuration and namespace isolation.

Pin internal packages to private registry (.npmrc):

# .npmrc — committed to repository
@company:registry=https://npm.company.internal/
# Only @company-scoped packages use internal registry
# Unscoped packages still resolve from public npm

# Or scope all packages to internal registry:
registry=https://npm.company.internal/
# (only if all packages are on internal registry)

Reserve package names on public registries:

# Publish a stub package to npm to reserve the namespace
npm publish --access public @company/utils
# Package has no code — just prevents takeover
# Set version to 9999.0.0 to always win version comparison

Subresource Integrity for direct script imports:

// package.json — use exact version pinning for internal packages
{
  "dependencies": {
    "@company/utils": "1.0.0"  // Exact version, not ^1.0.0
  }
}

Defense checklist:

  • Configure .npmrc to use private registry for all internal scopes
  • Reserve internal package names on public registries
  • Use scoped packages (@company/...) for all internal packages
  • Pin exact versions for internal packages
  • Monitor public registries for packages matching internal names

Knowledge Check

0/3 correct
Q1

How does a dependency confusion attack cause package managers to install malicious code?

Q2

What does configuring @company:registry=https://private.npm.company in .npmrc do?

Q3

What is the most effective way to prevent dependency confusion for an internal package called @acme/auth?

Code Exercise

Configure Private Registry Scope

The .npmrc file has no registry configuration for internal packages, leaving them vulnerable to dependency confusion. Add configuration to direct @mycompany-scoped packages to the private registry.

ini

Put it into practice

Find Dependency Confusion in your own code

Offensive360 SAST traces the tainted data flow behind this class of bug across 60+ languages and ships a secure fix with every finding. Public open-source projects scan free; teams can see it on their own repository in a 30-minute demo.