What Happened
On February 9, 2021, security researcher Alex Birsan disclosed a dependency confusion attack that compromised build systems at 35 organizations, including major technology companies. The attack exploited a flaw in how package managers resolve dependencies when using both public npm registries and private internal packages.
Birsan's method was straightforward: he identified internal package names used by target companies, then published malicious packages with identical names to the public npm registry. When developers' build systems installed dependencies, the package manager's default behavior pulled the public malicious version instead of the intended private package.
This attack succeeded because most organizations had not configured their package managers to prefer private registries or properly scope their internal packages.
Timeline
Pre-February 2021: Organizations developed internal npm packages with generic names like utils, common-lib, or company-specific identifiers. These packages existed only in private registries, but package manager configurations didn't prevent public registry lookups.
Attack Phase: Birsan published packages with matching names to public npm, setting high version numbers (e.g., 999.0.0) to ensure the public package appeared "newer" than private versions. Build systems automatically pulled these malicious packages during dependency installation.
February 9, 2021: Birsan disclosed the vulnerability publicly after successful proof-of-concept attacks against 35 companies. The malicious packages contained only telemetry code to demonstrate the exploit.
Post-Disclosure: Organizations scrambled to audit their package configurations and implement scoping or registry restrictions. The npm ecosystem began developing tools to detect and prevent these attacks.
Which Controls Failed
Registry Resolution Configuration: Organizations failed to configure .npmrc files or package manager settings to define registry resolution order. Without this configuration, npm defaults to checking the public registry for every package, even when private alternatives exist.
Package Scoping: Teams published internal packages without npm scopes (the @company-name/package-name format). Unscoped packages are vulnerable because npm cannot distinguish between public and private packages with identical names.
Dependency Verification: CI/CD pipelines lacked integrity checks to verify that installed packages matched expected sources. Build systems trusted whatever the package manager installed without validation.
Supply Chain Visibility: Organizations had no inventory of internal package names or monitoring to detect when identically-named packages appeared in public registries. The attack succeeded silently across multiple build cycles.
Least Privilege in Build Systems: Build environments had broad network access, allowing them to reach public registries even when they should only access internal infrastructure.
What Standards Require
NIST 800-53 Rev 5 addresses supply chain risk through control SR-3 (Supply Chain Controls and Processes), requiring organizations to "employ security and privacy controls and processes to protect against supply chain risks." The dependency confusion attack represents precisely the type of supply chain compromise this control aims to prevent.
SR-4 (Provenance) requires documenting and verifying the origin of system components. Your package manager should verify that @yourcompany/auth-lib comes from your private registry, not from an attacker's public package.
ISO/IEC 27001:2022 Annex A control 8.30 (Outsourcing) requires organizations to "address information security within supplier agreements." When you use public npm packages, you're outsourcing code to third parties. Your registry configuration determines whether you're accidentally outsourcing to attackers.
PCI DSS v4.0.1 Requirement 6.3.2 states that custom software must be developed based on industry standards and secure coding practices. Installing unverified packages from public registries during your build process violates this requirement—you're incorporating untrusted code into your payment processing systems.
For SOC 2 Type II auditors, dependency confusion represents a failure of CC6.6 (Logical and Physical Access Controls) and CC7.2 (System Monitoring). Your build systems granted unauthorized access to external code, and you lacked monitoring to detect the substitution.
Lessons and Action Items
Immediate Actions (This Week):
Audit your
.npmrcfiles in every repository and CI/CD environment. Add explicit registry configurations:@yourcompany:registry=https://npm.yourcompany.internal registry=https://registry.npmjs.org/Run snync or equivalent tooling to identify internal package names that exist unprotected in public registries. This open-source tool scans your package.json files and checks whether matching names exist publicly.
Implement package scoping for all new internal packages. Publish as
@yourcompany/package-nameand configure your private registry to be the authoritative source for your scope.
This Quarter:
Configure registry restrictions in your package manager. For npm, use the
--registryflag in CI/CD scripts or set environment variables to prevent accidental public registry access for scoped packages.Add integrity verification to your build pipelines. Generate and verify
package-lock.jsonornpm-shrinkwrap.jsonfiles, and fail builds if installed packages don't match expected checksums.Set up Verdaccio or similar private registry infrastructure if you're currently using only folder shares or basic authentication. Modern private registries provide scoping, access controls, and audit logging that prevent dependency confusion.
Ongoing:
Monitor public registries for packages matching your internal naming patterns. Set up alerts when new packages appear with names similar to your internal libraries.
Document your package sources as part of your software bill of materials (SBOM). Every package in production should have a verified origin—private registry, public registry, or vendor-supplied.
Train developers on the difference between scoped and unscoped packages. Make scoping mandatory in your package naming standards and code review checklists.
The dependency confusion attack succeeded because organizations treated package installation as a purely technical process rather than a security control. Your .npmrc file isn't just configuration—it's access control policy for your build systems. Treat it accordingly.



