Audit Dependencies with pip-audit and safety

Audit dependencies with pip-audit and safety — Secure development.

Focus: audit dependencies with pip-audit and safety

Sponsored

You've built a Python app that works perfectly in development. Then you run pip install on a fresh staging server, ship it, and months later discover a known critical vulnerability — in a transitive package you never even touched. The timer starts: patch, rebuild, redeploy, hope the exploit wasn't already used. This is exactly the pain that dependency auditing solves. In this lesson, you'll learn to audit dependencies with pip-audit and safety, so you catch those vulnerabilities before they ever reach production.

The problem this lesson solves

Every dependency you add to requirements.txt or pyproject.toml brings along its own trust obligations — and the dependencies of those dependencies. A single outdated requests version or a malicious urllib3 release can expose your data, your users, and your reputation. The Python package ecosystem is enormous and fast-moving; keeping track manually is impossible. The result is "dependency debt": silent vulnerabilities lurking in code you barely read.

The consequences are real. Supply-chain attacks are on the rise, and the average age of a known vulnerability in production systems is measured in months. The OWASP Top 10 lists "Vulnerable and Outdated Components" as a top risk — it's not just a theoretical concern. When an attacker scans your public endpoints, they often probe for known CVEs (Common Vulnerabilities and Exposures) before anything else.

The answer isn't to stop using packages — that's absurd. The answer is to make auditing a routine, automated, and fast part of your workflow. pip-audit and safety are the two most established tools for this job, and they fit naturally into your existing Python toolchain.

Core concept / mental model

Think of auditing dependencies as a security check at the border. Every package that enters your environment is a visitor; you want to verify their identity and check them against a list of known troublemakers before you let them stay.

  • pip-audit queries the OSV (Open Source Vulnerabilities) database via the OSV API. It examines your environment (pip-audit --local), a requirements file, or a pip-compiled lockfile.
  • safety (from pyup.io) uses its own curated Safety vulnerability database, which includes both CVE and non-CVE entries. It also offers a free-only tier for public projects and paid tiers with enhanced coverage.

Both tools compare the exact versions of your installed packages (or listed in your lockfile) against a collection of known vulnerable releases. If a version is not vulnerable, you're green. If it is, you get a clear report with the affected range and a fixed version.

Key terms you'll meet:

  • CVE — Common Vulnerabilities and Exposures, a public identifier for a specific security flaw.
  • Advisory — a formal notice about a vulnerability, usually includes affected versions and fix.
  • Transitive dependency — a package that a dependency depends on; often invisible in your requirements.txt.
  • Lockfile — a file that pins exact versions of every package and its dependencies (e.g., pip-tools output, Poetry's poetry.lock).

A practical mental model: your environment is a fleet of vehicles; pip-audit and safety are the inspection stations. You run them regularly, they inspect every vehicle, and they flag the ones with known safety recalls. You can't fix a recall you don't know about.

How it works step by step

The process is simple, but there are steps you shouldn't skip.

  1. Generate or maintain a full inventory — ideally a lockfile that pins exact versions of every direct and transitive package.
  2. Run a vulnerability scan — with pip-audit or safety, pointing at that inventory.
  3. Interpret the report — for each finding, note the package, the affected versions, and the recommended fixed version.
  4. Update or patch — upgrade to a non-vulnerable version, or apply a workaround if no fix exists yet.
  5. Re-run the scan — confirm the vulnerability is gone.
  6. Automate — integrate the scan into your CI pipeline, so a new PR that introduces a vulnerable dependency fails the build.
  7. Schedule recurring scans — new advisories are published daily; run audits at least weekly or on every deploy.

For pip-audit specifically:

  • It can audit your current environment (pip-audit --local), a requirements file (pip-audit -r requirements.txt), or a lockfile (pip-audit -r requirements.txt works if it's in requirements.txt format, but better to use project-specific lockfile support).
  • It uses a local cache of the OSV database, so it works offline after the first run.

For safety:

  • It accepts a requirements.txt file or a full frozen environment (safety check -r requirements.txt).
  • The free tier is limited; you may need an API key for advanced features like ignoring CVEs with a reason.

Hands-on walkthrough

Let's get our hands dirty. First, install both tools in a virtual environment to avoid polluting your global Python.

python -m venv .venv
source .venv/bin/activate  # Windows: .venv\Scripts\activate
pip install pip-audit safety

Now let's create a sample project with a known vulnerable package. We'll use an older, intentionally vulnerable version of requests for demonstration.

mkdir demo_project && cd demo_project
echo "requests==2.19.1" > requirements.txt

Now run pip-audit on the requirements file:

pip-audit -r requirements.txt

Expected output (verbatim may vary):

Found 1 known vulnerability in 1 package
Name   Version Fix  Vulnerability ID
requests 2.19.1 2.20.0  CVE-2018-18074

Yes — requests 2.19.1 has a known vulnerability (it would set max_retries in a way that could allow a proxy to exhaust your connection pool). pip-audit tells you the fixed version.

Now run safety on the same file:

safety check -r requirements.txt

Expected output:

+===================================================================+
 REPORT 
+===================================================================+
 Safety is using database generated... 
-> Scanning dependencies in your project...
-> Found 1 vulnerabilities
+===================================================================+
 Name   Version Fix  CVE  
 requests 2.19.1  2.20.0  CVE-2018-18074

Both tools flag the same issue. Now let's see a real fix in action. Update the requirement and re-scan:

echo "requests==2.22.0" > requirements.txt
pip-audit -r requirements.txt

Expected:

No known vulnerabilities found (0 packages)

Great — the vulnerability is gone.

But in reality, you'll have many more packages. Let's install a real project and audit your environment:

pip install flask==1.0.3  # old version, just for demo
pip-audit --local

You might see something like:

Found 3 known vulnerabilities in 3 packages
Name   Version Fix  Vulnerability ID
flask   1.0.3   1.1.0  CVE-2019-1010083
...

Now you know the drill: upgrade and re-scan.

Automating with CI

A key practice is to make these tools run automatically. Here's a minimal GitHub Actions workflow snippet:

name: Dependency Audit
on: [push, pull_request]
jobs:
  audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.11'
      - run: pip install pip-audit safety
      - run: pip-audit -r requirements.txt
      - run: safety check -r requirements.txt

This ensures every push is scanned. If an auditor finds a vulnerability, the build fails, forcing you to address it.

Compare options / when to choose what

Both tools serve the same purpose, but they have distinct strengths. Here's a comparison to help you decide:

Feature pip-audit safety
Database OSV (open, comprehensive) Safety DB (curated, includes non-CVEs)
Cost free, open source free tier (limited), paid for full features
Offline support yes (local cache) no (always queries API)
Output formats JSON, cyclonedx, etc. text, JSON
Best for CI pipelines, open-source projects Enterprises needing curated data, compliance
Integration pip-audit, pip-audit --local safety check, safety review

When to choose pip-audit: you're in a CI environment, you want a modern tool with a clean API, and you don't need the extra curated database. It's the default recommendation for new projects.

When to choose safety: you need legal/compliance reporting, want to ignore specific CVEs with a documented reason, or you're in a corporate environment that values the Safety database's extra entries. The paid tiers offer vulnerability analytics and a policy library.

Pragmatic approach: use both. Run pip-audit for a quick, open-source baseline, and run safety with a paid API key if you need advanced features. They're fast and cheap to run in CI.

Troubleshooting & edge cases

Even with powerful tools, you'll run into issues. Here are the most common ones and how to handle them.

1. False positives / false negatives

  • False positive: A package is flagged but you're not using the affected module, or you're on a non-vulnerable code path. Investigate the advisory before panicking. pip-audit includes a --fix flag that attempts to upgrade, but you should review the advisory to confirm the reachability.
  • False negative: No vulnerability found, but you suspect something. This happens when the database hasn't caught up with a new advisory. Update your local cache (pip-audit --refresh) and consider using multiple databases (both pip-audit and safety).

2. Vulnerabilities with no fix yet

Sometimes the advisory is real, but the package maintainer hasn't released a patched version. In that case:

  • Check for workarounds — often the vulnerability is only reachable under certain conditions; the advisory may suggest mitigations like "disable X" or "restrict network access".
  • Pin the vulnerable version with a comment explaining why you're accepting the risk, and set a reminder to revisit. Tools like safety let you ignore a specific CVE with a reason via the --ignore option, which is useful for documenting your decision.

3. Git dependencies or local packages

Neither tool can audit packages installed from Git URLs or local paths, because they don't have a version number that maps to the database. For these, you'll need to manually track or rely on the external source for advisories.

4. Large lockfiles slow to scan

The scan can be slow on huge projects. Use pip-audit's --local mode if you've already installed, or restrict to your runtime dependencies (exclude dev tools) to speed up CI.

5. Pipeline failing due to a known vuln in a dev-only package

If the vulnerability is only in a development dependency (e.g., pytest), you can separate dev requirements from production requirements, and only audit the production list in your critical path.

6. API rate limits (safety free tier)

safety's free tier has a limited number of queries per day. If you hit the limit, wait or upgrade. Alternatively, use pip-audit as your main scanner and reserve safety for pre-release checks.

7. Virtual environments and global installs

If you're auditing a project, always audit within its virtual environment (pip-audit --local). Auditing your global Python will produce a lot of noise and might fail due to system packages.

What you learned & what's next

You've now added a critical tool to your security belt: you can audit dependencies with pip-audit and safety to uncover known vulnerabilities before they become exploits. You learned how to generate a vulnerability inventory, run scans, interpret reports, fix issues, and automate the process in CI. You also learned how to compare the two tools and handle edge cases like false positives and unfixed vulnerabilities.

This directly reinforces your secure-development skills — auditing is part of a proactive security posture. The next lesson in this track will take you deeper into threat modeling, teaching you how to think like an attacker and systematically evaluate the attack surface of your application. With dependency auditing now automated, you'll have one less blind spot on your security journey.

Practice recap

Try auditing your own project right now: install pip-audit and safety, generate a lockfile with pip freeze > requirements.txt (or use pip-tools), then run both scanners. Fix at least one real vulnerability you find, and re-scan to confirm. Next, add the CI workflow from this lesson to one of your GitHub repositories.

Common mistakes

  • Only auditing direct dependencies — transitive dependencies are the majority of real-world vulnerabilities. Use pip-audit --local or a lockfile to capture the full dependency tree.
  • Ignoring negative output — assuming 'no vulnerabilities' means your code is secure. Auditing only checks known CVEs; you still need to test for logic flaws and coverage of your actual use of the package.
  • Running the auditor on a global Python environment — it will flag system packages and won't reflect your project's actual pinned versions. Always use a virtual environment and audit with --local or -r requirements.txt.
  • Skipping the re-scan after fixing a version — it's easy to forget, but only a second scan confirms the vulnerability is truly gone.

Variations

  1. Use pip-audit with the --fix flag to automatically attempt upgrading vulnerable packages to the first non-vulnerable version (when available).
  2. Use safety with --full-report to generate a detailed, human-readable report with CVE descriptions and recommended actions.
  3. For an audit-as-code approach, integrate pip-audit into a pre-commit hook or a GitHub Action that fails the build on any new vulnerability.

Real-world use cases

  • A Django web app undergoing a quarterly security review — run pip-audit -r requirements.txt in CI to catch new advisories against locked versions.
  • A data science pipeline with a large, often-ignored dependency tree — schedule a weekly pip-audit --local to detect vulnerabilities in packages like pandas or numpy that only surface in production.
  • A startup preparing for a SOC 2 audit — use safety with a paid plan to produce compliance-grade reports documenting ignored CVEs with justification.

Key takeaways

  • Auditing dependencies with pip-audit and safety is a fast, reliable way to discover known vulnerabilities in your Python project before attackers do.
  • Always audit the full dependency tree, including transitive dependencies, using a lockfile or --local in a virtual environment.
  • Fix vulnerabilities by upgrading to the recommended patched version, then re-run the audit to confirm.
  • Automate the audit in CI with a simple workflow to keep new vulnerabilities from entering your codebase.
  • Choose pip-audit for open-source, CI-friendly scanning; choose safety for curated data and compliance features.
  • Handle edge cases like false positives and unfixed advisories with investigation and documented risk acceptance.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.