You can audit your Python dependencies for known vulnerabilities, hash them to ensure their integrity, and then protect them from accidental or malicious modification.

Here’s a quick demonstration of how these security features work together.

# Create a virtual environment
python -m venv .venv
source .venv/bin/activate

# Install a package with a known vulnerability (for demonstration)
# Let's assume 'vulnerable-package' version 1.0.0 has a CVE.
pip install vulnerable-package==1.0.0

# Generate a requirements.txt file with hashes
pip freeze > requirements.txt

# Now, let's simulate a 'secure' installation using the requirements.txt
# First, remove the installed package
pip uninstall vulnerable-package -y

# Install from requirements.txt, which includes hashes
pip install -r requirements.txt

When you run pip install -r requirements.txt, Pip checks the SHA256 hash of vulnerable-package==1.0.0 against the one stored in requirements.txt. If they don’t match, the installation fails, preventing a tampered package from being installed.

The Problem: Supply Chain Attacks

The most significant threat to Python project security today isn’t your code; it’s the code you don’t write – your dependencies. A compromised dependency can introduce malware, steal credentials, or create backdoors into your system. Historically, Pip relied solely on package names and versions. If an attacker could trick PyPI into hosting a malicious package with the same name and version as a legitimate one (a "typosquatting" or "dependency confusion" attack), or if a legitimate package’s maintainer account was compromised, your project could inadvertently pull in malicious code.

The Solution: Audit, Hash, Protect

Pip has evolved to address these risks through a multi-layered approach:

  1. Auditing (Vulnerability Scanning):

    • What it is: Pip can check your installed dependencies against databases of known security vulnerabilities (CVEs).
    • How it works: It uses the pip audit command. This command queries the Python Packaging Advisory Database (a community-maintained database of known vulnerabilities) to find any security issues associated with the packages and versions you have installed.
    • Diagnosis:
      pip audit
      
      If vulnerabilities are found, pip audit will list them, along with their severity and affected versions.
    • Fix:
      pip install --upgrade "vulnerable-package>1.0.0"
      
      This command upgrades vulnerable-package to the latest version after 1.0.0, which presumably has the vulnerability patched. Pip audit doesn’t automatically fix; it informs you so you can manually upgrade.
  2. Hashing (Integrity Verification):

    • What it is: Pip can verify that the downloaded package file is exactly what it’s supposed to be, preventing the installation of a modified or corrupted file.
    • How it works: When you generate a requirements.txt file using pip freeze or pip-compile (from pip-tools), you can optionally include cryptographic hashes of the downloaded package files. Pip then uses these hashes to ensure the integrity of any package it downloads during installation.
    • Diagnosis: First, generate a requirements file with hashes. The --require-hashes flag is crucial.
      # Install a package
      pip install requests==2.28.1
      
      # Freeze with hashes
      pip freeze --require-hashes > requirements.txt
      
      Your requirements.txt will look something like this:
      requests==2.28.1 --hash=sha256:a7e1033321472a119426638927030f0646f4f5443a21974787554a7a00f48865
      
      During installation (pip install -r requirements.txt), Pip will download requests-2.28.1.tar.gz (or wheel) and compare its SHA256 hash to a7e1033321472a119426638927030f0646f4f5443a21974787554a7a00f48865. If they don’t match, the installation fails.
    • Fix: If an installation fails due to a hash mismatch, it usually means either:
      • The package on PyPI was changed after you generated the requirements file (rare and usually indicates a serious issue with the package).
      • There was a network interruption or corruption during download.
      • You are trying to install a package that wasn’t downloaded from the expected source. The fix is to regenerate your requirements.txt file from a known good state or to investigate the integrity of the download. If you are confident the package on PyPI is correct, you’d rerun the freeze command:
      pip freeze --require-hashes > requirements.txt
      
      Then retry your installation.
  3. Protecting (Pinning and Trusted Hosts):

    • What it is: This is about ensuring that the exact versions of your dependencies are installed, and that Pip is fetching them from trusted sources.
    • How it works:
      • Pinning: Using requirements.txt (especially with hashes) pins your dependencies to specific versions. This prevents "dependency hell" where new versions of dependencies introduce breaking changes or subtle bugs. It’s also a security measure because you know exactly what code you’re running.
      • Trusted Hosts: For environments with strict network policies or when using private package indexes, you can configure Pip to only trust specific hosts for package retrieval, using the trusted-host option in pip.conf or via the command line.
    • Diagnosis/Fix: To pin, you simply use pip freeze > requirements.txt (or pip-tools for more advanced dependency management). To configure trusted hosts (e.g., for a private index at my.private.repo.com): Create or edit ~/.config/pip/pip.conf (Linux/macOS) or %APPDATA%\pip\pip.ini (Windows).
      [global]
      trusted-host = my.private.repo.com
      index-url = https://my.private.repo.com/simple/
      
      This tells Pip to trust my.private.repo.com as a source for packages and to use its index-url.

The One Thing Most People Don’t Know

When you run pip audit, it’s querying a local cache of vulnerability data by default. This cache is updated periodically, but it might not always be the absolute latest information. For critical security audits, you’ll want to ensure you’re checking against the most up-to-date advisories.

To force Pip to fetch the latest vulnerability data before performing the audit, use the --disable-cache flag:

pip audit --disable-cache

This ensures that your audit is based on the freshest available security intelligence, rather than potentially stale cached data.

By combining pip audit with hashed requirements.txt files and proper pinning, you establish a robust defense against many common supply chain attacks targeting your Python projects.

The next logical step is to integrate these security checks into your CI/CD pipeline to automate vulnerability detection and prevent compromised code from reaching production.

Want structured learning?

Take the full Pip course →