Dependency confusion: how attackers hijack your internal packages without touching your network
In 2021, a security researcher named Alex Birsan published a paper describing how he had successfully installed unauthorized code on machines at Apple, Microsoft, Netflix, PayPal, Shopify, Tesla, and Uber — all without exploiting a single vulnerability in their software. He did it by uploading files to public package registries.
The technique is called dependency confusion. It remains one of the most underappreciated attack vectors in software supply chain security.
How package resolution works
When you run npm install my-internal-package, npm checks its configured registry for a package with that name. If your team uses a private registry for internal packages, npm checks both the private registry and the public npm registry, depending on how it's configured.
The critical behavior: most package managers, by default, prefer the version with the higher version number, regardless of whether it comes from the private registry or the public one.
This is the vulnerability.
The attack
If your company uses an internal npm package called @acme/auth-utils at version 1.2.3, an attacker can:
- Register the package name
@acme/auth-utilson the public npm registry (possible if the scope hasn't been claimed) - Publish a version
9.9.9— higher than any version your internal registry would plausibly have - Include malicious code in that version
- Wait for any developer or CI runner that tries to install
@acme/auth-utils
When the install runs, npm sees version 9.9.9 on the public registry and version 1.2.3 on the private registry. It installs 9.9.9. The malicious code runs.
Defenses
Claim your scope on npm. If your internal packages use a scoped name like @acme/, register that scope on the public npm registry.
Use frozen installs. Lockfiles pin exact package versions and registries. If you have a lockfile that resolves @acme/auth-utils to your private registry, an attacker publishing a higher version to the public registry won't affect you — as long as you use npm ci rather than npm install.
Use Veln. Veln's typosquat and publisher-identity checks compare every package being installed against your lockfile. The cooling gate catches the "high version number" pattern when a package has zero community installs.
Veln catches dependency confusion attacks by verifying publisher identity and community observations before any package installs.