Skip to content
← Blog

Technical explainer

Dependency confusion: how attackers hijack your internal packages without touching your network

2 min read

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:

  1. Register the package name @acme/auth-utils on the public npm registry (possible if the scope hasn't been claimed)
  2. Publish a version 9.9.9 — higher than any version your internal registry would plausibly have
  3. Include malicious code in that version
  4. 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.