Dependency confusion is a software supply chain attack in which malicious code infiltrates a build by exploiting how package managers resolve names across public and private registries. When an internal package like acme-utils exists in a company's private npm or PyPI registry but the build tool also checks a public registry, an attacker who publishes a package with the same name — and a higher version number — to the public registry can trick the resolver into pulling their code instead. No credentials, no phishing, no insider access required. Security researcher Alex Birsan proved the technique at scale in February 2021, collecting over $130,000 in bug bounties after compromising build pipelines at Apple, Microsoft, PayPal, Shopify, Netflix, Uber, and Yelp using nothing but publicly guessable package names. The attack remains a live threat today because most package manager defaults still favor the highest version number over the "trusted" source.
What Is Dependency Confusion?
Dependency confusion is a technique where an attacker publishes a package to a public registry (npm, PyPI, RubyGems, NuGet) using the same name as an internal, private package used by a target organization, causing the build system to fetch the attacker's malicious package instead of the legitimate internal one. The vulnerability doesn't live in the code itself — it lives in the resolution logic of the package manager. If a company's CI pipeline references internal-auth-lib without explicitly pinning it to a private registry or scope, and that name has never been claimed on the public npm registry, anyone can register it, publish a version numbered 9.9.9, and wait. The next npm install or pip install run in that pipeline may silently prefer the public copy because most default resolvers treat version recency as the deciding factor, not registry trust.
How Does a Dependency Confusion Attack Work?
A dependency confusion attack works in three steps: reconnaissance to find internal package names, registration of matching malicious packages on public registries, and passive waiting for automated builds to pull them in. Attackers commonly harvest internal package names from package.json, requirements.txt, .csproj files, error messages, leaked internal documentation, or even job postings that mention internal tooling names. Birsan's 2021 research pulled candidate names directly out of open-source repositories belonging to target companies, where internal dependency names appeared in plain text inside public package.json files committed by employees. Once a name is identified, the attacker publishes a package under that exact name to the public registry with an inflated version (for example, 4.0.1 when the internal package is at 1.2.0), embeds a payload that exfiltrates environment variables, hostnames, or SSH keys, and publishes it. No further action is needed — the next scheduled build or developer npm install does the rest, often inside a CI environment with far more network and credential access than a developer laptop.
Why Do Package Managers Fall for This?
Package managers fall for dependency confusion because their default configurations were built for open-source convenience, not multi-registry trust boundaries, and most will silently substitute a public package for a private one with the same name if the public one reports a newer version. Prior to 2021, npm's default resolution checked the registries configured in .npmrc in order, but a huge number of real-world configs pointed generically at the public npm registry for all unscoped names, with private registries used only for explicitly scoped packages (@acme/utils). Unscoped internal package names had no such protection. Python's pip behaves similarly with --extra-index-url: by default pip will consider all configured indexes and, absent --index-url exclusivity or a pinned hash, can select whichever source offers a matching or higher version. This is exactly the gap that let Birsan compromise PayPal's build environment, where an internal Node.js module name was discoverable and unscoped in the public PayPal GitHub repository.
What Real-World Incidents Have Exploited Dependency Confusion?
Two incidents define the public record on dependency confusion: Alex Birsan's February 2021 multi-company disclosure and the PyTorch torchtriton attack of December 30, 2022. In the 2021 case, Birsan and collaborator Justin Gardner identified over 35 organizations vulnerable to the technique, with Microsoft, Apple, PayPal, Shopify, Netflix, Yelp, Tesla, and Uber among the confirmed, paying targets; Apple alone paid a $30,000 bounty. In the PyTorch incident, attackers registered a malicious package named torchtriton on the public PyPI registry — matching the name of an internal dependency that PyTorch's nightly build pulled from PyPI without a pinned private-index priority. Anyone running pip install torch via the nightly channel between December 25 and December 30, 2022, risked pulling the malicious package, which harvested SSH keys, hostnames, and environment variables and exfiltrated them via DNS. The PyTorch team disclosed the incident on December 31, 2022, and resolved it by claiming the name on PyPI as a placeholder — the same fix Microsoft now recommends broadly: register every internal package name in every public registry your ecosystem touches, even if you never intend to publish real code there.
How Can Organizations Detect and Prevent Dependency Confusion?
Organizations prevent dependency confusion primarily by enforcing registry scoping and namespace reservation, not by relying on package manager defaults. Concrete controls include: using scoped packages (@yourorg/package-name) for every internal dependency so resolution is unambiguous by design; setting .npmrc or pip.conf to pin internal names to a private registry exclusively rather than layering public and private sources; claiming placeholder packages under your organization's internal names on npm, PyPI, and other public registries your teams use, exactly as PyTorch did after the torchtriton incident; and generating and reviewing a Software Bill of Materials (SBOM) for every build so that a package's actual source registry — not just its name and version — is auditable after the fact. Detection matters as much as prevention: continuously diffing resolved package sources against an expected allowlist of internal registries will catch a confusion attempt the moment a build pulls from an unexpected source, before the payload executes in CI.
How Safeguard Helps
Safeguard closes the dependency confusion gap by combining automated SBOM generation and ingest with reachability analysis, so security teams see not just which packages entered a build but whether the resolved source matches the expected internal registry and whether the suspicious package's code is actually reachable from your application's execution paths. Griffin AI, Safeguard's analysis engine, flags anomalous version jumps and unscoped internal package names that are prime dependency confusion targets, prioritizing alerts by exploitability rather than burying teams in every registry mismatch. When a confusion risk is confirmed, Safeguard can open an auto-fix pull request that pins the dependency to its correct private registry source and scope, cutting remediation from a manual audit to a one-click merge. For teams managing hundreds of internal packages across npm, PyPI, and NuGet, that combination turns a namespace guessing game into a monitored, provable control.