In February 2021, Alex Birsan published research showing how he could get code execution at Apple, Microsoft, PayPal, and dozens of other companies by publishing public packages with the same names as their internal packages. The attack was simple: he uploaded packages to npm, PyPI, and RubyGems with names matching internal packages he discovered through leaked configuration files and public repositories.
When build systems tried to resolve these package names, they found the public versions and installed them instead of (or alongside) the internal versions. The public packages contained benign payload that phoned home to demonstrate the vulnerability, but a malicious attacker could have included anything.
This attack -- dependency confusion -- is preventable through proper naming conventions and registry configuration. But years later, many organizations still have not implemented adequate defenses.
Why Naming Matters
Dependency confusion works because package managers search public registries when a package is not found in the configured private registry. If your internal package is named analytics-service and an attacker publishes analytics-service on npm, your build system may install the public version.
The naming convention you choose for internal packages is your first line of defense. A good naming convention makes it obvious (to humans and to tooling) that a package is internal and should never be resolved from a public registry.
Naming Strategies
Scoped Packages (npm)
npm supports scoped packages: @company/package-name. Scoped packages cannot be confused with unscoped public packages because the scope acts as a namespace.
If you use scope @acme, an attacker cannot publish @acme/analytics-service without controlling the @acme scope on npm. As long as you own your scope on npm (even if you never publish public packages to it), your internal packages are protected.
Recommendation: Use scoped packages for all internal npm packages. Register your scope on npm even if you only use private packages.
Namespace Prefixes (Python, others)
Python does not have scoped packages like npm. The common approach is to use a company-specific prefix: acme-analytics-service or acme.analytics.service.
This does not prevent an attacker from registering the same name on PyPI, but it makes the attack harder because the prefix makes the package name obviously organizational, you can claim the prefix on PyPI to prevent others from registering it, and automated tools can be configured to reject any package with your prefix from public registries.
Maven Group IDs
Maven uses group IDs (like com.acme.internal) as namespaces. Group IDs on Maven Central must be verified against domain ownership, so an attacker cannot publish to com.acme.internal without controlling acme.com.
This provides natural protection against dependency confusion for Maven projects, as long as you use your organization domain as the group ID.
Go Module Paths
Go modules use the source repository path as the module name (like github.com/acme/analytics-service). This inherently prevents dependency confusion because the module path is tied to a specific repository.
Registry Configuration
Naming alone is not sufficient. You also need to configure your package manager to resolve internal packages correctly.
npm: Configure .npmrc to route scoped packages to your internal registry.
pip: Use --index-url for your internal registry and --extra-index-url for PyPI. Better yet, use a proxy registry that serves both through a single endpoint with internal packages taking priority.
Maven: Configure your settings.xml to use an internal repository mirror that proxies Maven Central.
Preventing Future Confusion
Register your namespaces on public registries. Even if you only use private packages, register your organization namespace on npm, PyPI, and other public registries.
Use allowlists for public packages. Configure your internal registry to only proxy approved public packages.
Scan for leaked internal names. Regularly search public repositories and CI/CD configurations for references to internal package names.
Monitor public registries. Watch for new packages that match your internal naming patterns.
How Safeguard.sh Helps
Safeguard.sh monitors public package registries for packages that match your internal naming patterns. Our platform detects dependency confusion risks by comparing your internal package inventory against public registry publications. When a potential confusion attack is detected, Safeguard.sh alerts your security team immediately. Combined with SBOM generation, you maintain full visibility into which packages are resolved from which registries.