Dependency Confusion: Prevention and Detection

Dependency confusion prevention controls package namespaces, registry routing, lockfiles, provenance, and build telemetry across private and public feeds.

Model confusion as a resolution-policy failure

Dependency confusion prevention starts before malware analysis. A build asks for a package name; client configuration, scope, registry priority, version rules, cache state, and lock data decide the source. If an internal name can resolve publicly, an attacker may publish that name and become an unintended candidate.

npm registry documentation explains that scope influences registry selection. Other ecosystems differ, so document actual resolver behavior rather than assuming one universal “private first” rule. This is a specific form of software supply chain attack.

Inventory private names and every resolver configuration

Export dependency manifests, lockfiles, private registry catalogs, CI images, developer configuration, proxy rules, and build egress. Identify internal packages that are unscoped, absent publicly, or allowed to fall through. Include transitive and build-time dependencies.

Use organization-controlled scopes where supported; npm scopes create namespaces and can route to a registry. Reserve important public names defensively only when policy and registry terms allow it; reservation is not a substitute for routing controls.

Bind names, registries, versions, integrity, and egress

Route private namespaces exclusively to approved registries. Fail closed when that registry is unavailable. Pin dependency versions and integrity hashes, require reviewed lockfile changes, disable unneeded lifecycle scripts, proxy approved public packages, and restrict build-network egress.

Produce build provenance and artifact signing that identifies resolved package URI, digest, registry, builder, and configuration. A signature proves who signed an artifact; provenance is needed to explain its inputs.

Detect public collisions and unexpected package origins

Monitor new public packages matching private names, but treat that as exposure intelligence rather than proof of execution. Alert when a build resolves a private namespace from a public host, changes registry configuration, regenerates a lockfile unexpectedly, downloads a new digest, or initiates unusual install-time network traffic.

Store package name, version, digest, registry URL, resolved URI, parent dependency, build ID, builder identity, lockfile commit, and provenance reference. Preserve registry and build evidence before removing a suspicious package.

Stop resolution, find every build, and replace artifacts

Block the package and external source, freeze affected builds, preserve package bytes and logs, and correct registry routing. Use digest and build ID—not package name alone—to locate artifacts, containers, caches, developer workstations, and deployments. Rotate credentials exposed to install scripts.

Rebuild from verified inputs on clean builders, publish new provenance, redeploy, and monitor. Apply lessons from container registry compromise when contaminated artifacts propagated downstream.

Test one private namespace without executing unknown code

Next step: choose one internal package family. Trace its resolver rules on CI and developer systems, confirm public fallback fails, verify lock integrity, and inspect provenance. Use a local controlled registry and inert test package; never publish a deceptive package to a public namespace for an unapproved test.

Frequently asked questions

Does a lockfile completely prevent dependency confusion?

No. It helps pin resolved inputs, but regeneration, missing integrity data, alternate platforms, install scripts, and registry misconfiguration can still create exposure.

Is a private registry sufficient protection?

Only if clients cannot fall through to public registries for private names and every build uses the intended configuration.