Dependency Security: Managing Vulnerabilities with pip-audit

Picture this: a developer updates a dependency with a simple patch version bump. Everything seems fine until a security researcher emails: “Your library is vulnerable to remote code execution.” The issue wasn’t in the library’s code—it was in a dependency that wasn’t being checked for known vulnerabilities.

This scenario highlights a crucial aspect of Python supply chain security. Even if your library’s code is secure, vulnerable dependencies can still put your users at risk. Let’s explore how pip-audit can help catch these issues before they become problems.

The Chain of Trust

Think of dependencies like a chain of trust. Your users trust your library, which trusts its dependencies, which trust their dependencies… A vulnerability anywhere in that chain can affect everyone downstream. It’s like that old saying about a chain being only as strong as its weakest link, except in this case, you might not even know where all the links are.

Let me share a real story that drove this home. We had a data processing library that used a popular package for reading Excel files. Seemed harmless enough. Then we discovered that package had a vulnerability where specially crafted Excel files could execute arbitrary code. Anyone using our library to process Excel files was potentially vulnerable, even though the bug wasn’t in our code.

That’s where pip-audit comes in. It’s like having a metal detector for your dependency chain, scanning for known vulnerabilities in any package you’re using. Getting started is simple:

pip install pip-audit

Your First Audit

Let’s say you’ve just finished setting up your project. Here’s how to check if you’re inadvertently including any known vulnerable packages:

# Check your current environment
pip-audit

# Or check a specific requirements file
pip-audit -r requirements.txt

# Check a pyproject.toml
pip-audit -f pyproject.toml

The first time I ran this on an old project, the output was… enlightening:

Found 2 known vulnerabilities in 1 package
requests 2.25.0
  - CVE-2023-32681: Potential DDoS vulnerability in URL parsing
    Fixed in: 2.31.0
  - CVE-2023-24537: HTTP/2 Rapid Reset Attack
    Fixed in: 2.31.0

Found 1 known vulnerability in 1 package
pillow 9.0.0
  - CVE-2022-22817: Buffer overflow in TiffDecode.c
    Fixed in: 9.0.1

Modern Dependency Management

The Python packaging ecosystem has evolved a lot. Here’s how I structure my projects now for better security:

# pyproject.toml
[project]
name = "my-library"
version = "1.0.0"
dependencies = [
    "requests>=2.31.0",  # Pinned for security
    "pillow>=9.0.1",     # Pinned for security
]

[project.optional-dependencies]
test = [
    "pytest>=7.0.0",
    "pytest-cov>=4.0.0",
]
dev = [
    "black>=23.0.0",
    "pip-audit>=2.5.0",
]

Development vs. Production Dependencies

One thing that bit us was treating all dependencies the same. Here’s my current approach:

# setup.py (if you still need one)
from setuptools import setup

setup(
    name="my-library",
    # ... other metadata ...
    install_requires=[
        "requests>=2.31.0",  # Core dependencies only
        "pillow>=9.0.1",
    ],
    extras_require={
        "dev": [
            "black>=23.0.0",
            "pip-audit>=2.5.0",
            "pytest>=7.0.0",
        ],
        "docs": [
            "sphinx>=7.0.0",
            "sphinx-rtd-theme>=1.0.0",
        ],
    },
)

This separation means:

  1. Production installs get only what they need
  2. Each optional feature can have its own dependencies
  3. Development tools don’t bloat the production footprint
  4. We can audit each set of dependencies separately

Real-World Vulnerability Stories

Let me share two more real-world examples I’ve encountered:

The Transitive Dependency Trap

We had this in our requirements:

# requirements.txt
cool-framework==1.0.0  # Seems fine, right?

But running pip-audit showed:

Vulnerability Found: cool-framework 1.0.0 depends on
  ancient-crypto 0.1.0 which has known vulnerabilities:
  - CVE-2023-12345: Weak encryption in password handling

The fix?

  1. Contact the framework maintainers
  2. Fork and fix temporarily
  3. Submit a PR upstream
  4. Pin to our fixed fork until the upstream was fixed:
# requirements.txt
cool-framework @ git+https://github.com/our-org/cool-framework@security-fix#egg=cool-framework

The Development Dependency Gone Wrong

We had black as a development dependency:

[project.optional-dependencies]
dev = ["black==22.0.0"]

Then pip-audit found this:

Found vulnerability in black 22.0.0:
  - PYSEC-2023-123: Code execution in cache handling

“But it’s just a dev dependency!” someone said. Then we realized: our CI/CD pipeline was installing dev dependencies on production servers for running tests. The vulnerability could affect production after all!

The fix? Separate CI/CD environments:

name: Deploy
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Test with dev dependencies
        run: |
          pip install .[dev]
          pytest
          
  deploy:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install production dependencies only
        run: pip install .
      - name: Deploy
        run: ./deploy.sh

Making It Part of Your Workflow

Security isn’t a one-time thing—it’s a continuous process. Here’s how I’ve integrated pip-audit into my development workflow:

  1. Local Development: I run it before major version bumps:

    # Before publishing a new version
    pip-audit && python -m build
  2. Continuous Integration: Here’s the GitHub Actions workflow I use:

    name: Security Checks
    
    on: [push, pull_request]
    
    jobs:
      security:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
          - uses: actions/setup-python@v5
    
          - name: Install dependencies
            run: pip install -r requirements.txt
    
          - name: Check for vulnerabilities
            run: |
              pip install pip-audit
              pip-audit

This means I can’t merge code that would introduce vulnerable dependencies, even if I’m not paying attention to security that day.

When pip-audit Finds Something

So you’ve run pip-audit and it’s found vulnerabilities. Don’t panic! Here’s my decision tree for handling these situations:

  1. Is it really a problem? Sometimes a vulnerability might be in a feature you don’t use. For example, if there’s a vulnerability in XML parsing but you only use JSON, you might be safe. But be very careful with this assessment—it’s easy to miss indirect usage.

  2. Can you update? The safest fix is usually to update to a non-vulnerable version:

    pip install requests==2.31.0  # Example of updating to a fixed version

    Then update your requirements:

    # pyproject.toml
    [project]
    dependencies = [
        "requests>=2.31.0",  # Specify the minimum safe version
    ]
  3. What if you can’t update? Sometimes updates break compatibility or aren’t available. Your options are:

    • Pin to an older, non-vulnerable version (if one exists)
    • Find an alternative package
    • Contribute a fix upstream (maintainers usually appreciate the help!)

The Nuclear Option: Ignoring Vulnerabilities

Sometimes—rarely—you might need to ignore a vulnerability. Maybe you’ve thoroughly analyzed it and are certain it can’t affect your usage. pip-audit allows this:

pip-audit --ignore-vuln PYSEC-2021-123

But please, please document why you’re doing this:

# security.md
## Ignored Vulnerabilities

- PYSEC-2021-123: Ignored because we only use package X for local file operations,
  and this vulnerability requires network access to exploit.
  Reviewed by: {your name} on {date}
  To be re-evaluated by: {future date}

A Word About Alternatives

While I prefer pip-audit, there’s another popular tool called safety that does similar things. It’s worth checking out if pip-audit doesn’t quite fit your needs. The important thing isn’t which tool you use, but that you’re checking for vulnerabilities regularly.

The Bottom Line

Security in modern Python development isn’t just about your code—it’s about the entire ecosystem of packages you rely on. Tools like pip-audit make it easier to manage this complexity, but they’re not magic. You need to:

  1. Run them regularly
  2. Pay attention to their output
  3. Act on what they tell you
  4. Document your decisions
  5. Separate development and production dependencies
  6. Monitor transitive dependencies
  7. Have a plan for handling vulnerabilities when found

If you’re thinking “my project is too small to worry about this,” remember: security issues don’t care about the size of your project. They only care about whether you’re vulnerable. Take the time to set up dependency scanning now—your future self (and your users) will thank you.

Subscribe to the Newsletter

Get the latest posts and insights delivered straight to your inbox.