# No packages removed from the npm registry (unpublished)

- **Pattern:** `ab-000953` (`dependency-supply-chain.security-vulns.no-unpublished-deps`)
- **Severity:** critical
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-000953
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-000953)

## Why it matters

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.

## Severity rationale

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.

## Remediation

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.

## Detection

- **ID:** `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:
  1. Scope your private packages under a unique organization scope: `@yourcompany/package-name`
  2. Configure `.npmrc` to always fetch your scoped packages from your private registry:
     ```
     @yourcompany:registry=https://your-private-registry.example.com
     ```
  3. Publish placeholder packages on the public npm registry for your internal package names to "claim" them.

  Review the npm [security advisories](https://github.com/advisories) regularly.

## External references

- cwe CWE-829
- cwe CWE-1357
- owasp A08
- slsa L2

Taxons: supply-chain

HTML version: https://auditbuffet.com/patterns/ab-000953
