Dependency confusion is a supply-chain attack where a package manager installs a public package instead of an organization's internal one of the same name, because the resolver was told about both places the name might live and had no rule saying the internal one always wins.
In 2021, researcher Alex Birsan demonstrated this against more than 35 companies — Apple, Microsoft, PayPal, Netflix, Uber, and Tesla among them — by publishing decoy packages under their internal package names on the public npm and PyPI registries. Internal build systems, configured to check the public registry alongside (or instead of) a private one, installed the decoys and executed Birsan's code with real credentials. Some paid out six-figure bug-bounty totals for the finding.
pip's own documentation names the risk directly: --extra-index-url tells pip to consider a second index in addition to the primary one, and "the additional repository can specify a version of a package which will be installed instead of the one from index-url." pip compares versions across every configured index and installs whichever reports the higher number for a given name — so a public package published under your internal package's name, at a higher version, wins.
pip install -r requirements.txt --extra-index-url https://pkgs.internal.example.com/simple/
If acme-internal-utils only exists on the internal index today, publishing acme-internal-utils version 999.0.0 on the public PyPI is enough to have pip install the attacker's version everywhere this command runs.
npm resolves an unscoped dependency name (no @org/ prefix) against whatever registry is configured for that project — typically set globally in .npmrc with a bare registry= line. That configuration is local to each machine and each CI runner. A developer's laptop with the internal .npmrc resolves an internal package correctly; a CI runner, a new hire's fresh laptop, or an automated dependency-update bot without that file falls back to the public npm registry for the exact same unscoped name — and if nobody has claimed it there, an attacker can.
--index-url with a single trusted index that proxies or mirrors the public packages you need, instead of --extra-index-url alongside the public index. Most private-index servers (Artifactory, Nexus, a private PyPI mirror) support this. Where that is not possible, pin exact versions with --require-hashes so an unexpected package cannot silently substitute.@your-org/package-name) — a scope is bound to your npm org and nobody outside it can publish under it, which removes the ambiguity entirely. Where an unscoped internal name already exists, claim it on the public registry as an empty placeholder so nobody else can.Paste your requirements.txt, pip.conf, package.json, and .npmrc into Dependency Confusion Check to find every --extra-index-url use and unscoped npm name at once, or run the CLI in your own checkout:
python3 check.py --fail-on-risk
That exits with a nonzero status if either pattern is present — wire it into your own CI to block a newly-added one before it merges. Already running a supply-chain platform like Socket? See how this checker differs from Socket — different layers, and worth running both.