What Is a Dependency Confusion Attack?

Author: Adrian KesslerPublished: Aug 27, 2026Updated: Aug 27, 202614 min read

A dependency confusion attack exploits software supply chains by tricking build systems into downloading malicious public packages instead of private enterprise components.

Featured image for What Is a Dependency Confusion Attack?
Featured image for What Is a Dependency Confusion Attack?

A dependency confusion attack exploits software supply chains by tricking build systems into downloading malicious public packages instead of private enterprise components.

A dependency confusion attack represents a severe software supply chain vulnerability where build automation tools mistakenly prioritize untrusted, publicly hosted code over proprietary internal packages. Modern software engineering relies on automated package managers to fetch external dependencies alongside proprietary modules. When internal naming conventions leak into public visibility, malicious actors can register identical package names with artificially inflated version numbers on public repositories like npm, PyPI, or RubyGems. Consequently, automated CI/CD pipelines inadvertently pull and execute untrusted code directly inside production environments. Securing the modern enterprise perimeter requires engineering leaders to understand how package managers resolve dependencies, identify operational misconfigurations, and implement defensive governance across the entire developer lifecycle.

Understanding Dependency Confusion Attacks

Dependency confusion, also classified as a dependency substitution attack, is a structural software supply chain risk inherent in hybrid dependency resolution mechanisms. In standard enterprise software development, organizations rarely build every functionality from scratch. Instead, engineering teams incorporate thousands of open-source packages alongside custom-built, proprietary libraries containing business logic, internal API clients, and security utilities.

To streamline integration, developers configure package managers—such as npm for JavaScript, pip for Python, or Bundler for Ruby—to query both private artifact repositories and central public indexes. The structural flaw arises when these tools do not maintain strict boundaries regarding where specific packages must originate. If a build tool queries multiple registries simultaneously without explicit namespace isolation, it defaults to preprogrammed resolution heuristics. In almost all default package manager implementations, the resolution heuristic prioritizes the highest semantic version number available across all reachable registries.

When an organization fails to enforce authoritative registry routing, it creates a systemic vulnerability. The build system assumes that any package sharing an expected identifier is legitimate regardless of its hosting source. Attackers exploit this blind trust by injecting adversarial code into public indexes using internal package names discovered through reconnaissance. Once pulled, these packages execute pre-install or post-install scripts with the full privileges of the build environment or developer workstation, turning a routine dependency update into an arbitrary code execution incident.

The Role of Package Managers in Software Supply Chains

Package managers are the automated backbone of continuous integration and continuous deployment (CI/CD) environments. They parse manifest files—such as @@CODE0@@, @@CODE1@@, or Gemfile—calculate dependency trees, resolve transitive requirements, and download artifacts. To optimize development velocity, package managers are designed to be flexible, supporting multiple upstream feeds and fallback endpoints.

However, this flexibility introduces significant risk when dual-feed configurations lack cryptographic verification or deterministic routing. When an enterprise configures an internal package repository alongside an open-source mirror, the package manager often treats all sources as a flat, single pool of available code. Without strict governance, the software supply chain becomes vulnerable to namespace collisions where external entities can override internal components without triggering basic perimeter alarms.

How the Dependency Substitution Mechanism Works

The fundamental mechanism behind dependency substitution relies on semantic versioning (SemVer) mechanics and registry resolution priority. An organization might maintain an internal utility titled @@CODE0@@ pinned internally at version @@CODE1@@. If an external attacker discovers this package name and uploads a malicious package named @@CODE2@@ with version @@CODE3@@ to a public repository, standard package manager configurations will detect the higher version number on the public index.

+-----------------------------------------------------------------------------------+
|                        DEPENDENCY RESOLUTION FLOW COMPARISON                       |
+-----------------------------------------------------------------------------------+
| Secure Proxy Routing:                                                             |
| Manifest -> Enterprise Gateway -> Exact Internal Namespace Match -> Private Repo  |
|                                                                                   |
| Vulnerable Dual-Feed Configuration:                                               |
| Manifest -> [Queries Public & Private Simultaneously] -> Highest Version (Public) |
+-----------------------------------------------------------------------------------+

Because automated build systems are frequently configured to pull the latest compatible version or default to the highest numerical release, the client queries the public registry, identifies version 99.0.0, downloads the archive, and executes embedded initialization scripts. This substitution occurs silently during pipeline execution without requiring developer intervention, compromised credentials, or perimeter penetration.

The Mechanics of a Dependency Confusion Attack

Executing a successful dependency confusion attack does not demand zero-day exploits or advanced cryptography breaking. It exploits deliberate configuration design choices present in foundational development tools. The attack lifecycle generally unfolds across three distinct, methodical operational phases: reconnaissance, artifact crafting, and automated ingestion.

Reconnaissance (Expose Internal Names)
   │
   ▼
Public Registry Registration (Upload Version 99.0.0)
   │
   ▼
Automated CI/CD Ingestion (Remote Code Execution)

Understanding this lifecycle allows DevSecOps teams to identify observation points, implement early detection filters, and break the kill chain before malicious code enters the deployment pipeline.

Phase 1: Identifying Private Enterprise Package Names

The initial phase requires passive or active reconnaissance to discover the exact names of proprietary internal libraries. Attackers gather this intelligence through several common leakage vectors:

  • Public Source Code Repositories: Developers occasionally publish proprietary source trees, configuration manifests, or sample client code to public platforms like GitHub or GitLab. Manifest files immediately expose private dependency names.

  • Decompiled Client-Side Assets: Production web applications, Single Page Applications (SPAs), and mobile binaries often contain bundled JavaScript sourcemaps, manifest files, or exposed client packages detailing build-time dependencies.

  • Public Package Manifests: Transitive internal references sometimes persist in open-source projects published by an enterprise's open-source initiatives.

  • Breached Data Dumps: Leaked configuration files, developer documentation, or issue tracker dumps provide extensive inventories of internal package nomenclature.

Phase 2: Publishing Malicious Packages to Public Registries

Once an attacker secures a list of internal package names, they query public registries to verify whether those names are unregistered globally. If an internal package such as corp-security-telemetry has never been claimed on the public npm or PyPI registry, the namespace is completely open.

The threat actor registers an account on the public ecosystem and uploads an empty or functional package under that exact name. The payload contains:

  1. An inflated version tag (e.g., @@CODE0@@ or @@CODE1@@) to outrank any realistic internal release.

  2. An execution hook (such as @@CODE0@@ or @@CODE1@@ in @@CODE2@@, or arbitrary execution inside Python's @@CODE3@@).

  3. A benign callback payload (for security researchers) or an obfuscated reverse shell / credential harvester (for malicious threat actors).

Phase 3: Exploiting Build System Priorities

The final phase occurs autonomously when the target organization runs standard software lifecycle operations. A developer executes npm install, a build server initiates a container compilation, or a CI/CD pipeline triggers an automated testing routine.

As the build tool initiates dependency resolution, it contacts its configured feeds. Seeing version @@CODE0@@ available on the central public registry, it bypasses the internal version @@CODE1@@ hosted on the corporate server. The package manager streams the public package into the environment, unpacks the archive, and immediately executes the installer script. Because build environments possess elevated privileges—often with access to environment variables, cloud deployment keys, and internal API tokens—the injected code can exfiltrate sensitive secrets within seconds of compilation.

Vulnerable Ecosystems: Where Do These Attacks Occur?

Dependency confusion is not confined to a single programming language or runtime environment. Any ecosystem that supports external registry indexing, custom internal modules, and automated package management can exhibit dependency substitution vulnerabilities if improperly configured. However, architectural differences between package managers dictate how vulnerabilities manifest and how they must be defended.

Ecosystem / ToolPrimary Public RegistryExploitation VectorRoot Misconfiguration
Node.js (npm / yarn)registry.npmjs.orgUnscoped package names & preinstall scriptsQuerying public upstream without scoped namespace isolation
Python (pip / poetry)pypi.org--extra-index-url multi-index resolutionUsing fallback search paths instead of locked private indexes
Ruby (Bundler / gem)rubygems.orgGlobal top-level source declarationsMultiple source blocks without explicit gem scoping
Java (Maven / Gradle)Maven CentralArtifact group ID collisionsUnverified repository declarations in build configurations
.NET (NuGet)nuget.orgMulti-feed package source priorityPackage source mapping disabled across enterprise feeds

Node.js (npm / yarn)

Primary Public Registry

registry.npmjs.org

Exploitation Vector

Unscoped package names & preinstall scripts

Root Misconfiguration

Querying public upstream without scoped namespace isolation

Python (pip / poetry)

Primary Public Registry

pypi.org

Exploitation Vector

--extra-index-url multi-index resolution

Root Misconfiguration

Using fallback search paths instead of locked private indexes

Ruby (Bundler / gem)

Primary Public Registry

rubygems.org

Exploitation Vector

Global top-level source declarations

Root Misconfiguration

Multiple source blocks without explicit gem scoping

Java (Maven / Gradle)

Primary Public Registry

Maven Central

Exploitation Vector

Artifact group ID collisions

Root Misconfiguration

Unverified repository declarations in build configurations

.NET (NuGet)

Primary Public Registry

nuget.org

Exploitation Vector

Multi-feed package source priority

Root Misconfiguration

Package source mapping disabled across enterprise feeds

Node.js and NPM

The Node.js ecosystem represents one of the most frequently targeted domains due to npm's massive scale and reliance on lifecycle installation hooks (@@CODE0@@, @@CODE1@@, @@CODE2@@). By default, npm packages that do not use a scope (e.g., @@CODE3@@ vs @organization/package-name) exist in a flat global namespace.

If an enterprise configures an internal registry using an upstream proxy without enforcing scoped packaging, an unscoped internal package name remains globally claimable on npmjs.com. When npm resolves dependencies, if the public upstream registry serves a package with a higher version than the internal server, npm defaults to downloading and executing the public artifact.

Python and PyPI

In the Python landscape, the package installer @@CODE0@@ historically introduced structural confusion when configured with the @@CODE1@@ argument. When developers or build configurations specify an internal index via @@CODE2@@ alongside the primary PyPI repository, @@CODE3@@ queries both indexes concurrently.

Unlike systems that strictly separate sources, @@CODE0@@ evaluates all packages returned from every index, sorts them strictly by version number, and pulls the highest version available. If an attacker places a version @@CODE1@@ on PyPI under an enterprise's private module name, pip fetches the public PyPI artifact despite the developer's intent to pull from the private server.

Ruby and RubyGems

Ruby's dependency manager, Bundler, handles dependencies via a @@CODE0@@. In earlier iterations and improper configurations, declaring multiple top-level @@CODE1@@ endpoints creates identical substitution ambiguities:

# Vulnerable Multi-Source Gemfile Pattern
source "https://rubygems.org"
source "https://gems.internal-corp.net"

gem "rails"
gem "corp-internal-auth" # Vulnerable to upstream substitution

When multiple global sources are declared, Bundler queries both repositories. If a gem exists in both locations, the resolver can inadvertently retrieve the public gem. Bundler has since introduced block-level source definitions, but legacy configurations and unpinned manifests continue to expose systems to dependency confusion attacks.

Dependency Confusion vs. Typosquatting: What is the Difference?

Software supply chain security includes numerous threat models, leading many organizations to conflate dependency confusion with typosquatting. While both techniques involve publishing malicious packages to open-source registries, their exploitation vectors, target mechanics, and remediation paths diverge significantly.

Comparison ParameterDependency ConfusionTyposquatting
Primary TargetAutomated build algorithms & package resolversHuman developers making typographical errors
Package Name UsedExact match of proprietary internal packageSlight variation of legitimate public package
Trigger MechanismNormal build execution requesting legitimate namesDeveloper misspelling a library name in a terminal or manifest
Example ScenarioInternal @@CODE0@@ overridden by public @@CODE1@@Public @@CODE0@@ mimicked by malicious @@CODE1@@
Exploitation DriverSemantic version prioritization (@@CODE0@@ > @@CODE1@@)Visual similarity and keyboard proximity
Primary DefensePackage scoping, private proxy routing, namespace reservationManifest linters, name validation, developer awareness

Primary Target

Dependency Confusion

Automated build algorithms & package resolvers

Typosquatting

Human developers making typographical errors

Package Name Used

Dependency Confusion

Exact match of proprietary internal package

Typosquatting

Slight variation of legitimate public package

Trigger Mechanism

Dependency Confusion

Normal build execution requesting legitimate names

Typosquatting

Developer misspelling a library name in a terminal or manifest

Example Scenario

Dependency Confusion

Internal @@CODE0@@ overridden by public @@CODE1@@

Typosquatting

Public @@CODE0@@ mimicked by malicious @@CODE1@@

Exploitation Driver

Dependency Confusion

Semantic version prioritization (@@CODE0@@ > @@CODE1@@)

Typosquatting

Visual similarity and keyboard proximity

Primary Defense

Dependency Confusion

Package scoping, private proxy routing, namespace reservation

Typosquatting

Manifest linters, name validation, developer awareness

Key Differences and Overlaps

The critical distinction lies in the target of deception. Typosquatting is a social engineering attack directed at developers. The attacker counts on human error—such as mistyping @@CODE0@@ as @@CODE1@@ or @@CODE2@@ as @@CODE3@@. If the developer never makes a typing mistake, the typosquatted package is never downloaded.

Conversely, dependency confusion requires no human error during installation. The developer or CI/CD script requests the exact, correctly spelled name of an authorized internal component. The failure occurs entirely within the automated resolver logic, which misinterprets trust boundaries across registries. Consequently, traditional security awareness training aimed at preventing typosquatting provides zero defense against dependency confusion.

Real-World Impact and Historical Case Studies

The practical feasibility of dependency confusion was demonstrated at an industry-wide scale in February 2021 by security researcher Alex Birsan. Through ethical vulnerability research, Birsan systematically compromised the internal build pipelines of over 35 major tech enterprises, including Apple, Microsoft, PayPal, Shopify, Netflix, and Tesla.

The Alex Birsan Discovery and Its Industry Repercussions

Birsan conducted reconnaissance on target organizations by analyzing public source code repositories, decompiled JavaScript bundles, and leaked manifest files. He extracted hundreds of proprietary internal package names that did not exist on public registries.

He then registered those exact names on npm, PyPI, and RubyGems, publishing packages configured with version 99.0.0. Each package contained a non-destructive Node or Python payload that collected internal reconnaissance telemetry—such as local hostnames, internal IP addresses, and MAC addresses—and transmitted this data via DNS callbacks to his authoritative nameserver.

+-----------------------------------------------------------------------------------+
|                            THE ALEX BIRSAN EXPERIMENT                             |
+-----------------------------------------------------------------------------------+
| 1. Discovered internal package names from client-side bundles & public commits.  |
| 2. Claimed empty namespaces on npm, PyPI, and RubyGems with version 99.0.0.      |
| 3. Automated build systems pulled high-version packages across 35+ tech giants.  |
| 4. Exfiltrated internal IP/host metadata via DNS queries to demonstrate RCE.     |
+-----------------------------------------------------------------------------------+

Within days, automated build servers and developer machines across dozens of Fortune 500 networks downloaded and executed the packages. The experiment proved that multi-billion dollar technology infrastructures possessed systemic blind spots regarding dependency resolution hierarchies, resulting in over $130,000 in bug bounties and triggering industry-wide security overhauls.

Enterprise Consequences: Data Exfiltration and Compliance Violations

While Birsan acted ethically, malicious threat actors leverage identical vectors to inflict severe operational and financial damage:

  • Production Environment Takeover: Build servers often possess deployment access keys (e.g., AWS IAM credentials, Kubernetes cluster tokens). Injected scripts can extract these tokens, granting attackers direct write access to cloud production infrastructure.

  • Source Code Exfiltration: Once code execution is established on CI/CD nodes, attackers can clone and exfiltrate proprietary source code repositories.

  • Regulatory Fines and Compliance Breaches: Inadvertent execution of untrusted third-party code in environments processing sensitive personal data triggers strict reporting mandates and severe liabilities under GDPR, ISO/IEC 27001, SOC 2 Type II, and PCI-DSS frameworks.

  • Downstream Customer Infection: If malicious dependencies are compiled directly into distributed client software or SaaS products, the attack transforms into a secondary supply chain compromise targeting end-users.

Comprehensive Mitigation Strategies for Enterprises

Mitigating dependency confusion requires moving away from default package manager behaviors and establishing strict, multi-layered supply chain governance. Relying on perimeter network firewalls is insufficient because package managers fetch dependencies over standard outbound HTTPS (Port 443). Organizations must implement definitive namespace control, repository configuration hardening, and automated pipeline verification.

1. Enforce Strict Package Scoping

The most effective architectural defense in ecosystems supporting namespaces (such as npm) is mandatory scoping for all internal code.

  • Use an organizational prefix for every internal package (e.g., @enterprise-name/internal-pkg).

  • Register and claim the official @@CODE0@@ scope on the public registry (@@CODE1@@).

  • Configure local @@CODE0@@ files to route all requests matching @@CODE1@@ strictly to the private registry while directing unscoped requests elsewhere.

Because public registries prevent unauthorized third parties from publishing under registered enterprise scopes, namespace squatting is neutralized at the root level.

2. Configure Dedicated Enterprise Artifact Repositories

Enterprises should avoid allowing individual developer machines or CI/CD pipelines to query public registries directly. Instead, all outbound package traffic must pass through a centralized binary repository manager, such as JFrog Artifactory, Sonatype Nexus, or AWS CodeArtifact.

Developer / CI Machine
        │
        ▼
[ Enterprise Artifact Gateway (Artifactory / Nexus) ]
        ├── Scoped Package Filter (@corp/* -> Internal Private Storage)
        └── Public Proxy Filter (External Packages -> Cached & Scanned Mirror)

Configure virtual repositories with strict routing rules:

  • Block Overlapping Namespaces: Instruct the artifact repository never to query public upstreams for packages belonging to internal naming patterns.

  • Disable Fallback Resolution: Configure private feeds as authoritative. If a package exists on the internal feed, the proxy server must entirely suppress external queries for that identifier regardless of reported upstream version numbers.

3. Preemptively Claim Internal Namespaces on Public Indexes

If legacy systems prevent immediate migration to scoped package names, organizations should defensively register their existing private package names on public registries.

  • Upload placeholder packages with minimal, non-functional metadata to public npm, PyPI, or RubyGems repositories.

  • Set public permissions to private or restrict write access to authorized organizational security accounts.

  • Apply defensive versioning (e.g., publishing version 0.0.0.1 or the maximum allowable version if permitted by registry rules) while locking the public repository from public installation.

4. Implement Deterministic Builds via Lockfiles and Hash Verification

Automated builds should never rely on floating or open-ended version ranges (e.g., @@CODE0@@ or @@CODE1@@).

  • Commit deterministic lockfiles (@@CODE0@@, @@CODE1@@, @@CODE2@@, @@CODE3@@) directly into version control.

  • Enforce continuous integration builds to run in strict verification mode (e.g., @@CODE0@@ instead of @@CODE1@@), ensuring packages are retrieved solely from URLs and cryptographic integrity hashes defined in the verified lockfile.

  • Enforce subresource integrity checks to halt build processes immediately if a package hash mismatches the repository signature.

Frequently Asked Questions

Is a dependency confusion attack considered a software supply chain attack?

Yes. Dependency confusion is a direct software supply chain vulnerability that compromises the integrity of third-party and first-party software components before application deployment.

Can perimeter firewalls prevent a dependency confusion attack?

No. Package managers pull malicious packages over standard outbound HTTPS connections (Port 443), bypassing traditional perimeter network firewalls unless deep application-layer inspection and domain whitelisting are enforced.

How does dependency confusion differ from dependency hijacking?

Dependency confusion registers unowned public names using version precedence, whereas dependency hijacking involves taking over existing, previously legitimate third-party packages through compromised maintainer accounts or expired domains.

Does publishing an internal package name on a public repository violate security policies?

Proactively registering internal package names as empty placeholder artifacts on public repositories is a recognized defensive strategy to prevent external attackers from claiming those namespaces.

Why do package managers default to downloading the highest version number?

Package managers are designed under semantic versioning principles to automatically resolve bug fixes and updates, assuming all reachable registries in a user's configuration are trusted endpoints.

How can DevSecOps pipelines detect dependency confusion vulnerabilities?

Teams can use automated software composition analysis (SCA) tools and manifest auditing scripts to continuously check whether internal, unscoped package identifiers are currently unregistered on public registries.

Is Python pip still vulnerable to dependency confusion attacks?

Python pip remains vulnerable if configured with @@CODE 0@@ pointing to internal feeds alongside PyPI. Teams must use @@CODE 1@@ pointing to a single managed proxy or apply strict index pinning configurations.

Can scoped packages completely eliminate dependency confusion in npm?

Yes. Using registered organizational scopes (e.g., @company/pkg ) completely eliminates dependency confusion on npm because third parties cannot publish packages under an organization-owned namespace.

Final Step

Launch your U.S. company with a structured execution plan

Use guided tools, operational support, and document workflows from one platform.

What Is a Dependency Confusion Attack? | Webizm