Prerequisites
- Target system, dependencies and environment configured.
Usage
Purpose
Dependency confusion is a supply-chain attack that exploits how package managers resolve names: if you use an internal package mycompany-utils and an attacker publishes a mycompany-utils to the public registry with a higher version number, your build may pull the attacker's public package instead of your private one — running their code in your build and production. This skill covers understanding and preventing dependency confusion, a widely-exploitable class discovered to affect major companies.
When to use it
Any organisation using internal/private packages alongside public registries (npm, PyPI, etc.) — which is most. It's a high-impact, easily-overlooked attack because it exploits default package-manager behaviour, not a bug in your code.
How the attack works
Package managers often check multiple sources (your private registry and the public one) and, by default, may pick the highest version regardless of source. So:
- An attacker learns an internal package name (often leaked in a public repo, a
package.json, or an error message).
- They publish a package with that exact name to the public registry, with a very high version number.
- Your build resolves the dependency, sees the attacker's higher-versioned public package, and installs it instead of your internal one — executing attacker code at install time (install scripts run) and in your application.
Procedure
- Understand your exposure. Do you use internal packages? Are their names discoverable (in public repos, client-side bundles, error messages)? Does your package manager check public registries for names you intend to be private? If yes to these, you're exposed.
- Claim your namespaces on public registries — the simplest defence. Register your internal package names (or a scope/namespace prefix) on the public registry yourself, so an attacker can't publish under them. Using scoped packages (
@mycompany/utils) with a reserved scope prevents the public registry from serving an attacker's package under your scope.
- Configure the package manager to resolve internal packages only from your private registry. Explicitly scope which packages come from where (npm
.npmrc scope-to-registry mapping, pip index configuration), so internal names are never resolved from the public registry. This removes the confusion at the resolution level.
- Use a single, controlled registry (a proxy/virtual registry). Front both public and private packages through one managed registry (Artifactory, Nexus) that you control, with rules preventing public packages from shadowing internal names. This centralises and controls resolution.
- Pin and verify. Lockfiles with integrity hashes (the lockfile-integrity skill) ensure you install the exact package you expect; verify sources.
- Don't leak internal package names. Keep
package.json/requirements with internal names out of public repos and client bundles where feasible — name discovery is the attacker's first step.
- Test your exposure. Check whether your build would pull a public package over an internal one of the same name (safely) — if it would, the confusion path is open.
Cheatsheet
attack: internal pkg `mycompany-utils` + attacker publishes SAME name to PUBLIC registry
at a HIGHER version -> your build pulls THEIRS (highest-version-wins default) -> attacker code runs
how: package managers check multiple sources + default to highest version regardless of source
attacker: learn internal name (leaked) -> publish public w/ huge version -> your build installs it
defend
CLAIM your namespaces on public registries (or reserved SCOPE: @mycompany/utils) — simplest
SCOPE resolution: internal names resolve ONLY from private registry (.npmrc / pip index config)
SINGLE controlled registry (Artifactory/Nexus proxy) — rules stop public shadowing internal
PIN + verify (lockfile integrity hashes)
DON'T LEAK internal package names (public repos, client bundles, errors) — name = attacker's step 1
TEST: would your build pull a public pkg over an internal same-name one?
Reading the exposure
- Internal packages resolvable from the public registry = the confusion path is open; an attacker who learns the name and publishes a higher version gets their code into your build. The core vulnerability, and it exploits default behaviour, not a bug.
- Unclaimed internal namespaces on public registries = an attacker can publish under your internal names; claiming them (or a reserved scope) is the simplest, most effective defence.
- Package manager checking public registries for private names = the mechanism of the attack; scope resolution so internal names only come from your private registry, and the confusion can't happen.
- Internal package names leaked (in a public
package.json, a client bundle, an error) = the attacker's first step accomplished for them; keep internal names private where feasible.
- No single controlled registry = resolution is harder to govern; a proxy/virtual registry with anti-shadowing rules centralises control.
- Scoped/claimed namespaces, private-only internal resolution, controlled registry, integrity-pinned = the confusion attack is closed.
Pitfalls
- Relying on package-manager defaults. The default highest-version-wins-across-sources behaviour is exactly what the attack exploits; you must explicitly scope resolution or claim namespaces. Defaults leave you exposed.
- Not claiming internal namespaces publicly. Leaving your internal names unregistered on the public registry lets an attacker publish under them; claim them or use reserved scopes.
- Leaking internal package names. Name discovery is the attacker's first step; internal names in public repos, client bundles, or error messages hand it to them.
- Mixed public/private resolution without scoping. If internal names can resolve from public, the confusion path is open; scope which packages come from where.
- Assuming it's a niche issue. Dependency confusion affected major companies precisely because it exploits universal default behaviour; treat it as a real, common exposure.
References
- Alex Birsan's dependency confusion research (the disclosure that named the attack)
- npm scopes /
.npmrc, pip index configuration, and private-registry documentation
- Artifactory/Nexus virtual-registry configuration
- The lockfile-integrity and package-repo-hardening skills
Inputs
- Relevant source code, logs, network traces, or system specifications.
Outputs
- Analysis findings, security audit report, or generated code artifacts.
1---2name: dependency-confusion3description: Use when protecting against dependency confusion attacks — where an attacker publishes a public package matching your internal package name and your build pulls theirs instead.4---5678## Prerequisites9- Target system, dependencies and environment configured.1011## Usage12### Purpose1314Dependency confusion is a supply-chain attack that exploits how package managers resolve names: if you use an internal package `mycompany-utils` and an attacker publishes a `mycompany-utils` to the *public* registry with a higher version number, your build may pull the attacker's public package instead of your private one — running their code in your build and production. This skill covers understanding and preventing dependency confusion, a widely-exploitable class discovered to affect major companies.1516### When to use it1718Any organisation using internal/private packages alongside public registries (npm, PyPI, etc.) — which is most. It's a high-impact, easily-overlooked attack because it exploits default package-manager behaviour, not a bug in your code.1920### How the attack works2122Package managers often check multiple sources (your private registry and the public one) and, by default, may pick the *highest version* regardless of source. So:231. An attacker learns an internal package name (often leaked in a public repo, a `package.json`, or an error message).242. They publish a package with that exact name to the public registry, with a very high version number.253. Your build resolves the dependency, sees the attacker's higher-versioned public package, and installs *it* instead of your internal one — executing attacker code at install time (install scripts run) and in your application.2627### Procedure28291. **Understand your exposure.** Do you use internal packages? Are their names discoverable (in public repos, client-side bundles, error messages)? Does your package manager check public registries for names you intend to be private? If yes to these, you're exposed.302. **Claim your namespaces on public registries — the simplest defence.** Register your internal package names (or a scope/namespace prefix) on the public registry yourself, so an attacker can't publish under them. Using scoped packages (`@mycompany/utils`) with a reserved scope prevents the public registry from serving an attacker's package under your scope.313. **Configure the package manager to resolve internal packages only from your private registry.** Explicitly scope which packages come from where (npm `.npmrc` scope-to-registry mapping, pip index configuration), so internal names are *never* resolved from the public registry. This removes the confusion at the resolution level.324. **Use a single, controlled registry (a proxy/virtual registry).** Front both public and private packages through one managed registry (Artifactory, Nexus) that you control, with rules preventing public packages from shadowing internal names. This centralises and controls resolution.335. **Pin and verify.** Lockfiles with integrity hashes (the lockfile-integrity skill) ensure you install the exact package you expect; verify sources.346. **Don't leak internal package names.** Keep `package.json`/requirements with internal names out of public repos and client bundles where feasible — name discovery is the attacker's first step.357. **Test your exposure.** Check whether your build would pull a public package over an internal one of the same name (safely) — if it would, the confusion path is open.3637### Cheatsheet3839```40attack: internal pkg `mycompany-utils` + attacker publishes SAME name to PUBLIC registry41 at a HIGHER version -> your build pulls THEIRS (highest-version-wins default) -> attacker code runs4243how: package managers check multiple sources + default to highest version regardless of source44 attacker: learn internal name (leaked) -> publish public w/ huge version -> your build installs it4546defend47 CLAIM your namespaces on public registries (or reserved SCOPE: @mycompany/utils) — simplest48 SCOPE resolution: internal names resolve ONLY from private registry (.npmrc / pip index config)49 SINGLE controlled registry (Artifactory/Nexus proxy) — rules stop public shadowing internal50 PIN + verify (lockfile integrity hashes)51 DON'T LEAK internal package names (public repos, client bundles, errors) — name = attacker's step 152 TEST: would your build pull a public pkg over an internal same-name one?53```5455### Reading the exposure5657- **Internal packages resolvable from the public registry** = the confusion path is open; an attacker who learns the name and publishes a higher version gets their code into your build. The core vulnerability, and it exploits default behaviour, not a bug.58- **Unclaimed internal namespaces on public registries** = an attacker can publish under your internal names; claiming them (or a reserved scope) is the simplest, most effective defence.59- **Package manager checking public registries for private names** = the mechanism of the attack; scope resolution so internal names only come from your private registry, and the confusion can't happen.60- **Internal package names leaked** (in a public `package.json`, a client bundle, an error) = the attacker's first step accomplished for them; keep internal names private where feasible.61- **No single controlled registry** = resolution is harder to govern; a proxy/virtual registry with anti-shadowing rules centralises control.62- **Scoped/claimed namespaces, private-only internal resolution, controlled registry, integrity-pinned** = the confusion attack is closed.6364### Pitfalls6566- **Relying on package-manager defaults.** The default highest-version-wins-across-sources behaviour is exactly what the attack exploits; you must explicitly scope resolution or claim namespaces. Defaults leave you exposed.67- **Not claiming internal namespaces publicly.** Leaving your internal names unregistered on the public registry lets an attacker publish under them; claim them or use reserved scopes.68- **Leaking internal package names.** Name discovery is the attacker's first step; internal names in public repos, client bundles, or error messages hand it to them.69- **Mixed public/private resolution without scoping.** If internal names can resolve from public, the confusion path is open; scope which packages come from where.70- **Assuming it's a niche issue.** Dependency confusion affected major companies precisely because it exploits universal default behaviour; treat it as a real, common exposure.7172### References7374- Alex Birsan's dependency confusion research (the disclosure that named the attack)75- npm scopes / `.npmrc`, pip index configuration, and private-registry documentation76- Artifactory/Nexus virtual-registry configuration77- The lockfile-integrity and package-repo-hardening skills7879## Inputs80- Relevant source code, logs, network traces, or system specifications.8182## Outputs83- Analysis findings, security audit report, or generated code artifacts.