Dependency confusion attacks work by registering a malicious package on the public npm registry using the same name as your private internal package. When npm resolves dependencies, it may fetch the public (attacker-controlled) version instead of your private one — silently installing code that exfiltrates credentials or establishes a backdoor. This attack class has compromised systems at Microsoft, Apple, PayPal, and dozens of other organizations. OWASP A08 (Software and Data Integrity Failures) and CWE-829 (Inclusion of Functionality from Untrusted Control Sphere) define the failure. SLSA L2 requires that every dependency be verifiable against a trusted source. An @yourcompany/internal-lib in package.json with no private registry configured in .npmrc is an open invitation for this exact attack.
Critical because dependency confusion attacks have been executed successfully against major organizations and result in silent installation of attacker-controlled code on developer machines and CI systems.
Scope all private packages under a unique organization namespace and configure .npmrc to route that scope exclusively to your private registry:
# .npmrc
@yourcompany:registry=https://your-private-registry.example.com
For each private package name, also publish a placeholder package on the public npm registry to claim the name and prevent an attacker from registering it. Use npm publish --access public with a package body that simply redirects to the private source or contains a security notice.
ID: dependency-supply-chain.security-vulns.no-unpublished-deps
Severity: critical
What to look for: Check package.json dependencies and devDependencies against known patterns of unpublished or removed packages. Look for: packages with highly unusual names (single characters, keyboard walks like asdf, random strings), packages that appear to be private namespaced packages installed from the public registry (e.g., @yourcompany/internal-tool listed in dependencies but no private registry configured in .npmrc), and packages in the lock file whose resolved URLs point to non-standard registries. Also check .npmrc for registry= pointing to a non-https://registry.npmjs.org URL without clear business justification. Count all instances found and enumerate each.
Pass criteria: All dependencies resolve to standard npm registry, no obviously unpublished package patterns detected, and any private scoped packages have a corresponding private registry configured. At least 1 implementation must be confirmed.
Fail criteria: A dependency appears to reference a package name that is likely unpublished or was a private internal name accidentally published to a public registry. Or .npmrc points to a custom registry without explanation.
Skip (N/A) when: No package.json detected.
Detail on fail: "Package '@acme/internal-lib' in dependencies but no private registry is configured in .npmrc — this may be vulnerable to dependency confusion" or "Registry in .npmrc points to http://localhost:4873 (Verdaccio) — verify this is intentional"
Remediation: Dependency confusion attacks occur when an internal package name is registered on the public npm registry by an attacker. When npm resolves the package, it may pick up the malicious public version instead of your private one.
To protect against this:
@yourcompany/package-name.npmrc to always fetch your scoped packages from your private registry:
@yourcompany:registry=https://your-private-registry.example.com
Review the npm security advisories regularly.