BIPI
BIPI

Dependency Confusion Still Works in 2026: The Quiet Wave of Attacks

Cybersecurity

Five years after the original Microsoft, Apple, and Tesla disclosure, dependency confusion is still landing on enterprises that mix private and public registries. Automated scans found 200+ vulnerable orgs in 2025 alone.

By Arjun Raghavan, Security & Systems Lead, BIPI · September 7, 2024 · 7 min read

#dependency-confusion#supply-chain

A logistics platform we work with shipped a new internal package called 'company-tracking-utils' to their private npm registry in March. They forgot to scope it. Within 48 hours, a public package with the same name appeared on npmjs.org. Version 99.9.9. The maintainer was a freshly-created account with no other packages.

Their CI didn't pull the malicious version because they'd configured the registry properly. But two developer laptops did, when engineers ran 'npm install' without specifying a registry. The malicious package phoned home with hostname, username, and the contents of ~/.aws/credentials.

Why this still works

The original technique from Alex Birsan's 2021 disclosure has not been patched. It can't be: it's a fundamental issue with how package managers resolve names when both a private and a public registry are configured. If both have a package called 'foo', npm will pick whichever has the higher version. PyPI has similar behaviour with pip's index URL handling.

The 2024-2025 wave was different from earlier exploitation. Attackers stopped manually researching targets and started running automated scans. They scrape GitHub for package.json files referencing internal packages (anything with an org-specific naming pattern like '@acme/internal-foo' that doesn't exist on npmjs.org). They publish a public package with the same name and a high version. They wait.

How the scans find your org

  • GitHub code search for package.json files in public repos owned by your org
  • npm registry queries for unscoped package names that look corporate
  • Leaked package-lock.json files in pastes, S3 buckets, mobile app teardowns
  • JS source maps in production that reference internal package paths
  • GitHub Actions logs that print package names during install

If your private packages are published unscoped on the public npm namespace squat-list, you're already on someone's automated scan. The most painful version of this attack: an attacker squats the name preemptively, waits for you to ship the actual package internally, and now any developer who installs it without specifying the registry pulls the malicious version.

Defences that work

Scope every internal package. On npm that means '@yourorg/package-name' and the scope is registered to your private registry. Configure .npmrc with @yourorg:registry=https://your-private-registry pointing the scope at your registry, and never let it fall back to npmjs.org for that scope.

On Python, use a private index that supports the --extra-index-url isolation, or run a proxy like devpi or Artifactory in pull-through mode where internal packages are explicitly mapped. Pip's default behaviour of trying every index in order and picking the highest version is the root cause; configuration that mirrors PEP 708 (Index Hosting Policies) helps.

Catching it in CI before it spreads

Lock files are necessary but insufficient. A package-lock.json pinning version 1.2.3 of @yourorg/foo to a checksum is fine, but what about the day a developer regenerates the lock file and pulls the malicious 99.9.9? Hash pinning helps, integrity-required configs help, but the durable fix is registry isolation.

We add a CI step that fails if any package not under the @yourorg scope appears in node_modules with a name that matches an internal naming pattern. It's three lines of grep against the lock file. Cheap, catches the attack class even if other defences fail.

What to do this week

  1. List every internal package name across npm, PyPI, and any other registries you use
  2. Check each one on the public registry; claim any unscoped names you can
  3. Migrate unscoped internal packages to scoped names with a deprecation period
  4. Configure registry resolution to fail closed (no public fallback for internal scopes)
  5. Add a CI check that flags resolution from the public registry for internal-pattern names

Dependency confusion is a 2021 vulnerability that became a 2025 industrial process. The technique is unchanged. The defences are unchanged. The reason it works is the same reason it always has: enterprise package management is configuration, and configuration drifts.

Read more field notes, explore our services, or get in touch at info@bipi.in. Privacy Policy · Terms.