Safeguard
Application Security•

ASVS 5.0 V15.2.4: Stop Dependency Confusion at the Registry

ASVS V15.2.4 (Level 3) asks that every dependency, including transitive ones, comes from the expected repository. Scoped registries, lockfiles and tests.

Safeguard Security Team
3 min read

ASVS 5.0 V15.2.4 requires that third party components and all their transitive dependencies come from the expected repository, internal or external, with no risk of a dependency confusion attack. It is a Level 3 requirement, aimed at high assurance applications. The practical answer is to pin each package name to one registry, lock exact versions with hashes, and make the build fail when anything resolves elsewhere.

Why it matters

Dependency confusion works because package managers, by default, may look in several registries and pick the highest version number. If your internal package is called acme-billing-utils and an attacker publishes a public package with that name and a higher version, a careless configuration installs theirs. Install scripts then run on your build server.

The attack needs no access to your systems, only knowledge of an internal package name. Names leak through public repositories, error messages and job postings.

How to implement it

Control the source for every name. For npm, bind your internal scope to your private registry and claim the scope on the public registry as well:

# .npmrc
@acme:registry=https://registry.internal.example.com/
registry=https://registry.npmjs.org/
always-auth=true

For pip, avoid --extra-index-url, which mixes sources. Use a single index that proxies upstream and applies allow rules:

# pip.conf
[global]
index-url = https://pypi.internal.example.com/simple/

Then lock content, not just versions. Python supports hash checking:

pip-compile --generate-hashes requirements.in
pip install --require-hashes -r requirements.txt

For npm, commit package-lock.json and install with npm ci, which refuses to change the lock file. In Maven, use a single mirror in settings.xml and a repository manager rule that blocks proxying of your own group id from the internet.

Transitive dependencies count. The lock file covers them, so review lock file changes in pull requests and have CI fail when a resolved URL points at an unexpected host. The free in-browser dependency scan at https://gold.safeguard.sh/scan lists the full tree so you can see what a manifest really pulls in.

How to test it

  1. Take an internal package name and publish a harmless package with the same name and a very high version to a local test registry.
  2. Run a clean install in CI with the real configuration. It must resolve the internal one.
  3. Parse the lock file and assert that every resolved URL host belongs to an allowlist.
  4. Confirm an install with a tampered hash fails.
  5. Verify your public scopes and names are registered so nobody else can take them.

Common mistakes

  • Using an extra index option so two registries are searched equally.
  • Committing a lock file but installing with a command that rewrites it.
  • Protecting direct dependencies while a transitive one comes from a git URL or a tarball on a random host.
  • Forgetting build tooling and base image packages.

Quick check

Does a private proxy registry alone fix this?

Not unless it has rules deciding which names may come from upstream. Otherwise it simply forwards the public package with the higher version.

Why is this Level 3 and not Level 1?

ASVS reserves it for applications with the highest assurance needs, where supply chain control is expected as a mature practice.

Sources

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.