Dependency confusion (Alex Birsan, 2021 — hit Apple, Microsoft, Netflix, PayPal and others) exploits how build tools resolve a package name when it exists in more than one registry — a company's private/internal feed and the public registry (npm, PyPI, RubyGems…):
resolve(name):
candidates = [private.lookup(name), public.lookup(name)]
if policy == "highest-version-wins":
return max(candidates, key = semver) # ← installs whichever is newer, from EITHER source
if policy == "registry-pinned":
return private.lookup(name) # public candidate never even considered
if policy == "scoped":
return private.lookup("@internal/"+name) # public registry has no such namespace to squat
An internal package like acme-auth-sdk@2.4.0 normally has no public counterpart. An attacker simply publishes acme-auth-sdk@9.9.9 to the public registry — no breach required, just a name guessed from a leaked package.json, job posting, or GitHub org. Under a naive "take the newest version across all configured registries" resolver, every CI build and every developer's npm install silently pulls the attacker's code instead, because a higher public version number always outranks the real internal one.
- Highest-version-wins — the actual vulnerable default of several package managers before 2021; the resolver only compares semver, never registry trust.
- Registry pinned — an explicit scope/registry mapping (
.npmrc, pip.conf) tells the tool "this name only ever comes from our private feed" — the public candidate is discarded before any version comparison happens.
- Scoped namespace — publishing under a reserved scope (
@internal/acme-auth-sdk) removes the collision entirely: the public registry has no path for that scope, so there is nothing to squat.
The blast radius here is every consuming service that runs install against the same resolver output in the same window — one poisoned resolution, many compromised builds. This 2D diagram uses the exact same resolution rule as the 3D version: a service is only compromised when the policy is "highest-version-wins" and the attacker's version number is strictly greater than the private package's fixed version (2.4).