Supply chain
What is dependency confusion?
Dependency confusion is an attack in which a build installs an attacker's public package instead of your private one with the same name. Nobody makes a typing mistake. The attacker only has to learn an internal package name and publish it to a public registry with a high version number, and a misconfigured build does the rest.
Updated
How a dependency confusion attack works
- Find an internal name. Private package names leak through public code, JavaScript bundles, error messages and manifests left in repositories.
- Publish the same name publicly. The attacker registers it on npm, PyPI or another public registry, with a very high version number.
- Wait for a build. A package manager set up to check both the internal and the public registry finds two candidates, and prefers the higher version.
- Run inside the company. The package's install script runs on a developer's machine or a CI runner, with access to whatever is there.
The research that named it
In February 2021, security researcher Alex Birsan published the technique. He had used it to get code running inside more than 35 organizations, including Apple, Microsoft and PayPal, under bug bounty programs or with permission. It worked across all three languages he tested, and attackers began copying it within days of publication.
Why it happens
The root cause is a package manager that treats several registries as equal. The classic example is Python: pip's
--extra-index-url adds a second source next to PyPI, and pip may install the public package if it finds
one with the same name.
# Unsafe for private packages: both indexes are candidates
pip install --extra-index-url https://pypi.internal.example/simple my-internal-lib
# Safer: one index you control, which proxies public packages
pip install --index-url https://pypi.internal.example/simple my-internal-lib
npm has the same issue with unscoped internal names, while a scope tied to your registry avoids it.
# .npmrc: everything under @acme comes only from your registry
@acme:registry=https://npm.internal.example/
How to prevent dependency confusion
- Use one source of truth. Point builds at a single internal registry that proxies public packages, and configure it so that internal names never resolve publicly.
-
Use scopes or namespaces. Own a scope such as
@acmeon the public registry, and map it to your private one. - Reserve your names. Register internal package names on public registries, so that nobody else can.
-
Lock and verify. Commit a lockfile and use hash checking, such as pip's
--require-hashes, so that a different package fails the install. See what a lockfile does and does not do. - Limit where builds can connect. If a build can reach only your registry, it cannot fetch the attacker's package at all. See GitHub Actions egress policy.
- Check what you leak. Search public code and client bundles for your internal package names.
Dependency confusion vs typosquatting
Typosquatting relies on a person mistyping a public package's name. Dependency confusion uses the exact name of a private package and relies on how the build is configured. Both reach the same place: attacker code running in your build. See what is a supply chain attack for how they fit with other attacks.
How to tell if you were affected
Look for internal package names that resolved from a public registry, or versions that do not exist in your internal one. That is hard to do from a lockfile alone, because the question is where a package actually came from during the build.
How CRACI helps
CRACI works at the point where the package is fetched. As your GitHub Actions runner, it enforces an egress policy with typed sources for registries, so a build can be limited to the registries you allow. It also records every package each job fetched into an SBOM, so you can see what entered a build. CRACI does not detect that a package is malicious, so keep your registry configuration as the first line of defense. See software supply chain security best practices.
Dependency confusion: frequently asked questions
What is a dependency confusion attack?
An attack in which a public package with the same name as a private one gets installed by mistake, because the package manager checks both registries and prefers the higher version.
How is dependency confusion different from typosquatting?
Typosquatting relies on someone mistyping a public package name. Dependency confusion uses the exact name of a private package and relies on a build configuration that checks public registries.
How do you prevent dependency confusion?
Use a single internal registry, scoped package names, lockfiles with hash checks, and rules that limit which registries your builds can reach. Reserving your internal names publicly also helps.
Is --extra-index-url safe?
Not for private packages. It makes both indexes candidates, so a public package with the same name can be installed. Use --index-url with one index you control.
Who discovered dependency confusion?
Security researcher Alex Birsan published the technique in February 2021, after using it against more than 35 organizations under bug bounty programs or with permission.
Limit where your builds can fetch from
Allow only the registries a GitHub Actions job needs, and see every package it pulled in.
Book a demo