DEV Community

Cover image for Dependency Confusion: Protecting npm and PyPI Packages
Mustafa ERBAY
Mustafa ERBAY

Posted on • Originally published at mustafaerbay.com.tr

Dependency Confusion: Protecting npm and PyPI Packages

Open-source dependencies used in software development processes form the foundation of modern applications. However, these dependencies have also become a significant vector for supply chain attacks. Dependency Confusion is a security vulnerability that arises when a package exists with the same name in both private and public registries, leading to malicious packages being installed inadvertently.

This vulnerability is particularly triggered when the names of private packages used in internal projects of large companies conflict with publicly accessible packages. Build systems or package managers often tend to select the package with higher priority or a newer version, allowing a malicious attacker's fake package to be unknowingly incorporated into your project. This situation can lead to serious data breaches and system compromises.

What is Dependency Confusion and How Does It Occur?

Dependency Confusion is a security vulnerability encountered when managing external dependencies of a software project. This type of attack relies on the principle that package managers (like npm, pip) mistakenly pull a package from a public repository when a package with the same name exists in both a private and a public repository. The attacker, by guessing or leaking internal package names of a company, uploads a malicious package with that name to a public package repository (npm registry or PyPI).

Later, when an in-house developer's build process or package installation command is executed, the package manager experiences confusion when deciding which source to download the package from. Typically, package managers prioritize public repositories by default or prefer a higher version number. This leads to the malicious package being included in the project's dependency list, potentially resulting in malicious actions such as code execution or data exfiltration.

ℹ️ Important Note

This type of attack was first discovered in 2021 by Alex Birsan and was successfully used to infiltrate the systems of many large tech companies. This clearly demonstrated that supply chain security not only involves protecting our own code but also all external dependencies we use.

At the core of this vulnerability lie the default behaviors of package managers and configuration deficiencies. When multiple package sources are used, clearly defining the package resolution order and preferred sources is vital. Otherwise, attackers can exploit this ambiguity to inject their malicious code into projects.

Ways to Protect Against Dependency Confusion in the npm Ecosystem

The npm ecosystem is central to dependency management in JavaScript and Node.js projects, and thus is particularly susceptible to Dependency Confusion attacks. A malicious npm package can infiltrate all layers of an application.

1. Using Scoped Package Names

Scoped packages in npm are one of the most effective ways to reduce the risk of dependency confusion. Scoped packages are named in the @scope/package-name format and are typically directed to a private registry. This clearly distinguishes them from packages with the same name in the public registry.

npm install @my-company/my-private-package
Enter fullscreen mode Exit fullscreen mode

In your project's .npmrc file, you can specify which registry a particular scope should be directed to. For example:

```ini title=".npmrc"
@my-company:registry=https://registry.my-company.com/
//registry.npmjs.org/:_authToken=${NPM_TOKEN}




This configuration ensures that all packages within the `@my-company` scope are pulled from `registry.my-company.com`, while all other packages are obtained from the default npm registry. Thus, even if your internal package names are used by someone else in the public registry, their retrieval from the correct source is guaranteed. This method simplifies package name management, especially in large organizations, and prevents conflicts.

### 2. `package-lock.json` and Dependency Locking

The `package-lock.json` file provides a complete snapshot of your application's dependency tree. This file records the exact version, hash value, and source (registry URL) of each dependency. The `npm ci` command installs dependencies using the `package-lock.json` file and ignores the `package.json` file, which increases consistency in build systems.

> **⚠️ Beware of Version Bumping Risk**
>
> While the `npm ci` command never modifies the `package-lock.json` file, the `npm install` command can update the `package-lock.json` file if dependencies in `package.json` do not match `package-lock.json` or if newer versions are available. Therefore, if your `package-lock.json` file is missing the URL for a private package and there is a higher-version public package, the `npm install` command can still pose a risk. It is important to lock your private packages with their correct URLs within `package-lock.json`.

Including the `package-lock.json` file in the version control system ensures that all developers and CI/CD environments use the same set of dependencies. This reduces the risk of a malicious package exhibiting different behaviors in different environments. However, this file needs to be regularly checked and updated to ensure it is current and correct.

### 3. npm Registry Prioritization and `always-auth`

npm has the ability to configure multiple registries. In addition to `registry` and `scope`-based settings, you can also adjust general behaviors. The `always-auth=true` setting ensures that npm always attempts to connect to a registry that requires authentication. This makes it harder for malicious packages in the public registry to be mistakenly preferred due to a non-authenticated configuration.



```ini title=".npmrc"
registry=https://registry.npmjs.org/
@my-company:registry=https://registry.my-company.com/
always-auth=true
_authToken=${MY_COMPANY_NPM_TOKEN}
Enter fullscreen mode Exit fullscreen mode

In this configuration, the default registry is set to the public npm registry, but the my-company scope is directed to the private registry. The always-auth=true setting ensures that authentication is always performed when connecting to the private registry. This prevents attackers from anonymously infiltrating your private registry with malicious packages. Furthermore, securely managing environment variables like NPM_TOKEN used in CI/CD environments is also part of this strategy.

Ways to Protect Against Dependency Confusion in the PyPI Ecosystem

In Python projects, PyPI (Python Package Index) and the pip package manager are widely used. As with npm, the PyPI ecosystem requires various measures against Dependency Confusion attacks.

1. Private PyPI Servers and pip Configuration

In many corporate environments, private PyPI servers (e.g., Artifactory, Nexus, devpi) are used to host private Python packages. These servers provide a secure environment for internal packages. When using the pip command, you can specify these private indexes with the --extra-index-url or --index-url arguments.

pip install --extra-index-url https://pypi.my-company.com/simple/ my-private-package
Enter fullscreen mode Exit fullscreen mode

--extra-index-url allows pip to search for packages from the specified URL in addition to the main PyPI registry. If the private package name conflicts with a package on the main PyPI, pip usually prefers the highest version number. This situation makes a Dependency Confusion attack possible if an attacker uploads a higher-version package to the public PyPI.

To mitigate this risk, a safer approach is to use --index-url to designate only the private PyPI server as the primary source, ensuring pip only searches for packages from there. However, in this case, PyPI.org might need to be added with --extra-index-url to access external open-source packages.

```ini title="pip.conf or pip.ini"
[global]
index-url = https://pypi.my-company.com/simple/
extra-index-url = https://pypi.org/simple/




This configuration ensures that `pip` first looks at the in-house PyPI and then, for packages not found there, redirects to PyPI.org. This order helps keep your internal packages secure.

### 2. Hash Checking in `requirements.txt` File

Specifying hash values for packages in the `requirements.txt` file provides an additional layer of defense against supply chain attacks. When used with `pip`'s `--require-hashes` flag, `pip` only installs packages with the specified hash value. If a package's hash value does not match, the installation process is halted.



```text title="requirements.txt"
my-private-package==1.0.0 --hash=sha256:abcdef...
requests==2.28.1 --hash=sha256:fedcba...
Enter fullscreen mode Exit fullscreen mode

This approach prevents a malicious attacker from injecting a package with a different hash value, even if it has the same name and version number. Hashes verify the integrity and authenticity of the package. However, for this method to be effective, the hashes in the requirements.txt file must be correctly generated and kept up-to-date. This can be automated in CI/CD processes.

3. Package Naming Strategies

PyPI does not have a direct mechanism similar to @scope in npm. Therefore, when naming your private packages, it is important to choose unique and unpredictable names. Using a corporate prefix (mycompany-) or a GUID-like identifier can make it harder for attackers to easily guess package names.

mycompany-data-processor
internal-api-client-xyz
Enter fullscreen mode Exit fullscreen mode

Such a naming strategy reduces the likelihood of a public package with the same name being created on PyPI. However, this is not a complete prevention but a step towards reducing risk. The strongest protection is provided by correct registry configuration and hash checks.

Automated Monitoring and CI/CD Integration for Supply Chain Security

Dependency Confusion attacks can occur at any point in the software development lifecycle. Therefore, automated monitoring and security measures integrated into CI/CD (Continuous Integration/Continuous Deployment) processes are critical.

1. Automated Dependency Auditing and Update Tools

Tools like Dependabot (GitHub) and Renovate continuously monitor your project's dependencies and automatically create pull requests when new versions or known vulnerabilities are detected. These tools can help detect not only new versions but also potential Dependency Confusion vulnerabilities (if configured correctly).

For these systems to work correctly, authentication credentials for your private package registries must be securely provided. Otherwise, these tools cannot update or audit your private packages. Furthermore, carefully reviewing and approving pull requests generated by these tools prevents any malicious changes from infiltrating the system.

2. Security Checks in CI/CD Pipelines

Your CI/CD pipeline is where dependencies are installed and built. Therefore, integrating security checks here is vital.

Diagram

Some controls that can be applied in the pipeline:

  • Private Registry Enforcement: Ensure dependencies are pulled from the correct registries using commands like npm config get registry or pip config list.
  • Hash Verification: Check the integrity of dependencies using commands like npm audit or pip install --require-hashes.
  • Package Name Conflict Check: Run a script that regularly checks if the names of your internal packages exist in the public registry. This can provide an early warning when a new internal package is created or an existing package's name changes.
  • Security Scan (SCA): Software Composition Analysis (SCA) tools (e.g., Snyk, Black Duck) scan for known vulnerabilities in open-source dependencies used. These tools can also detect potential Dependency Confusion risks.

These controls help detect malicious packages before they reach the production environment.

Security Best Practices and Organizational Measures

In addition to technical solutions, organizational processes and developer awareness play a critical role in protecting against Dependency Confusion attacks.

1. Naming Conventions

Adopting a consistent and unique naming convention for private packages across the organization is important. This reduces the likelihood of private packages conflicting with packages in public registries. For example, adding a specific prefix (@mycorp/, mycorp-) to all internal packages can be a good start.

Applying this rule not only to new packages but also to existing ones prevents long-term confusion. Organizing training for developers to adhere to these rules and using automated linting/validation tools is beneficial.

2. Regular Security Audits and Inventory Management

Regularly inventorying all dependencies used (both internal and external) and subjecting them to security audits is important. These audits can reveal potential risks and outdated dependencies.

These audits should also question the reliability of externally sourced packages. Dependencies from unknown or suspicious sources should not be used.

3. Developer Training and Awareness

Educating developers about Dependency Confusion attacks and general supply chain security is crucial. These trainings help developers adopt secure coding practices and recognize potential risks.

  • Package Naming Rules: Explain why these rules must be followed.
  • .npmrc and pip.conf Configurations: Teach how to correctly set up these files and why they are important.
  • Usage of package-lock.json and requirements.txt: Emphasize the benefits of dependency locking mechanisms and the necessity of keeping them updated.
  • Recognizing Suspicious Packages: Teach what research should be done before installing an unknown package (download count, author, last update date, etc.).

In this way, developers can contribute to the overall security of the project by being on the front lines of the security layer.

Conclusion

Dependency Confusion attacks clearly demonstrate the complex and evolving nature of software supply chain security. In popular ecosystems like npm and PyPI, taking proactive measures at both technical and organizational levels is essential to protect against such attacks. Solutions like scoped packages, private registry configurations, dependency locking files, and CI/CD integration significantly narrow the attack surface.

In addition to these measures, continuous developer education and security awareness are equally important. Always questioning the reliability of every dependency used and considering potential risks forms the foundation of a robust security posture. Let's remember that software security is not limited to the code we write ourselves; it begins and ends with every external component we use.

Official Resources

Top comments (0)