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:
- Production installs get only what they need
- Each optional feature can have its own dependencies
- Development tools don’t bloat the production footprint
- 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?
- Contact the framework maintainers
- Fork and fix temporarily
- Submit a PR upstream
- 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:
Local Development: I run it before major version bumps:
# Before publishing a new version pip-audit && python -m build
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:
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.
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 ]
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:
- Run them regularly
- Pay attention to their output
- Act on what they tell you
- Document your decisions
- Separate development and production dependencies
- Monitor transitive dependencies
- 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.