Skip to main content

Free 30-min security demo  — We'll scan your real code and show live findings, no commitment Book Now

Offensive360
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