Dependency confusion
When it applies
The target builds software using internal package names that aren't published publicly, on an ecosystem (npm, PyPI, RubyGems, Maven, NuGet) where a public registry is also consulted. If you can learn an internal name and publish it publicly, their resolver may fetch your package → RCE in their build/CI.
Why it works
Many package managers, misconfigured, prefer the highest version across all configured registries
— public included. Publishing internal-lib@99.0.0 to the public registry can outrank the private
internal-lib@1.2.3, so builds pull and execute your code (install scripts run automatically).
Method
- Harvest internal names: leaked
package.json/requirements.txt/pom.xml, source maps and JS bundles (→recon-js-analysis), error messages, public repos,.npmrc/registry config. - Confirm the name is unclaimed on the public registry (npm/PyPI/etc.).
- Publish a benign PoC package under that exact name with a high version, whose install/postinstall step makes an OOB callback (DNS/HTTP to your collaborator) including hostname/user — no destructive payload. This proves execution if their build pulls it.
- Wait for the callback from their build/CI environment → confirms confusion.
- Scoped packages:
@company/pkgon npm needs the scope unclaimed; note the config that would prevent it.
Gotchas
- Keep the payload benign and identifiable (your marker only) — you're proving exec, not attacking. Mind program RoE (many bug-bounty programs have specific rules for this).
- Proper configs (scoped registries,
--registrypinning, namespace ownership) prevent it — the finding is the missing control. - Take down the package after reporting.
Verify success
An OOB callback from the target's build/CI proving your public package was resolved and its code executed.
References
Alex Birsan "Dependency Confusion"; npm/PyPI scoping & registry-pinning docs.