Skip to content

Package Namespace Confusion Guard

Resolution rule — instantiates Transitive Trust Boundary Hardening

Binds each dependency name to its legitimate publisher and source registry, so a same-named or look-alike package from the wrong place can never be resolved in.

Before a build ever pins or verifies a package, a resolver has to decide which package a name refers to — and that decision is an attack surface. Package Namespace Confusion Guard defends the name-to-source binding: it fixes which registry, namespace, and publisher legitimately own a given name, so a resolver cannot be tricked into pulling an impostor. Its defining move is guarding the identity of the source, not the version or the bytes — the step before a lockfile pins anything. It exists specifically to defeat dependency confusion and typosquatting, where an attacker publishes a package whose name collides with, or closely resembles, a legitimate one in a registry the resolver also happens to search.

Example

A company builds internal packages under the scope @acme/*, published only to its private registry. An attacker publishes a package named @acme/auth to the public registry, at version 99.0.0. A naive resolver that searches both public and private indexes and prefers the highest version pulls the attacker's public package straight into the build — the essence of dependency confusion. The guard forecloses this: it declares that the entire @acme/* namespace resolves only from Acme's internal registry, with no public fallback, so the public @acme/auth is never even eligible regardless of its version. The name is bound to its rightful source, and the impostor has nothing to resolve into.

How it works

Maintain an authoritative map of name → owning registry and publisher, and enforce source-scoping at resolution time: internal names come only from the internal source, an internal-looking name offered by an external registry is refused, and names that are near-misses of known packages are flagged. The guard governs the impersonation and substitution dimension — is this the real package from the real source? — leaving which version and are the bytes signed to other mechanisms.

Tuning parameters

  • Scope granularity — protect whole namespaces (org scopes) versus individual package names. Namespace-level scoping is stronger and cheaper to maintain than per-name rules.
  • Resolver precedence — internal-first with no public fallback for internal names, versus a merged search. Disabling public fallback for owned names is the specific fix for dependency confusion.
  • Similarity sensitivity — how aggressively look-alike and typo names are blocked. Higher sensitivity catches more typosquats but raises false positives on legitimately-similar names.
  • Name-onboarding gate — who may register a new internal name, and whether owned names are also claimed defensively on public registries.
  • Public-mirror policy — whether external packages are proxied through a controlled mirror that cannot serve internal names.

When it helps, and when it misleads

Its strength is closing a class of attack that pinning and signing structurally miss: both act after a name has resolved, so they faithfully lock or verify whatever the resolver already chose — including an impostor picked at the very first resolution. This guard secures that first step.

Its failure mode is that it protects only the names it knows about: a brand-new internal package not yet scoped, or a genuinely ambiguous third-party name, can still slip through, and over-aggressive similarity blocking generates false positives that erode trust in the control. The classic misuse is assuming a private registry alone solves the problem while leaving public fallback enabled — the exact gap dependency-confusion attacks exploit.[1] The discipline: default-deny public fallback for owned namespaces, and defensively register the names you care about so no one else can claim them.

How it implements the components

  • intermediary_role_registry — it maintains the authoritative registry of which publisher and source registry legitimately own each package name, and resolution is checked against it.
  • dependency_and_substitution_inventory — it specifically guards the substitution dimension of the dependency inventory, preventing a same-named or look-alike package from being swapped in for the genuine one.

It does not pin exact versions or digests (trust_scope_contract, Dependency Lockfile and Allowlist); it does not verify signatures or provenance (Artifact Signature Verification, Provenance Attestation Check); and it does not enumerate the full component set for audit (Software Bill of Materials Review).

Notes

Name-binding and version-pinning are complementary halves and both are needed: Dependency Lockfile and Allowlist can faithfully pin the wrong package if the name resolved to an attacker before the pin was written. This guard secures the step before the pin — whose package — and the lockfile secures the step after — which version.

References

[1] Dependency confusion is the named attack in which a resolver that searches both a public and a private index for the same name can be induced to prefer a public impostor (often via a higher version number). The structural fix is source-scoping owned names with no public fallback — a control on name resolution, not on the artifact's contents.