The "Internal" Namespace Exploit
In 2021, security researcher Alex Birsan published a technique that allowed him to execute code inside the internal networks of Apple, Microsoft, PayPal, Tesla, Uber, and dozens of other companies — without exploiting any vulnerability in their code. He exploited the resolution logic of package managers.
The Setup:
- A company has an internal private library named
mycompany-authhosted on their private Artifactory instance. - Their
package.jsonlists"mycompany-auth": "^1.0.0". - Their build servers are configured to check both the private registry and the public npmjs.org registry (a common configuration for convenience).
The Attack:
- Birsan registered the name
mycompany-authon the public npmjs.org. - He published version
99.0.0(containing a harmless proof-of-concept payload — in a real attack, this would be malware). - The build server runs
npm install. - npm's resolution logic: "I see
mycompany-authv1.0 in the private registry and v99.0 in the public registry. The public one is a higher version number." - npm installs v99.0 from the public internet.
- The attacker's code executes inside the company's internal build environment.
The attack required no credentials, no phishing, and no zero-days. It exploited a design assumption: that higher version numbers are always better.
Mitigations
1. Scoped packages (most effective for npm/yarn):
Rename internal packages to use a company-specific scope: @mycompany/auth instead of mycompany-auth. Configure the package manager so that entire scope routes exclusively to the private registry, with no public fallback for it at all. Public registries will never have packages under a private scope if this is configured correctly, because the resolver never even asks them.
2. Namespace reservation:
Register your internal package names on the public registry as empty placeholder packages. This prevents an attacker from claiming the name. It is a defensive measure, not a primary control.
3. Private registry priority with no public fallback:
Configure your build tooling to only use the private registry, which itself proxies and vets public packages. Developers never reach npmjs.org directly.
4. Version pinning:
If you pin to [email protected] exactly (not a range), a v99.0 on the public registry will not be selected. This is defense-in-depth — pinning alone does not fully prevent confusion attacks in all package managers, but it reduces the attack surface.
Dependency confusion is a favorite interview topic because it tests whether candidates understand how package managers resolve dependencies, not just how to use them. A strong answer explains the version-number resolution logic that makes the attack work, names the scoped-package mitigation for npm, and mentions that the fix is registry configuration — not application code changes.