Safeguard
Open Source Security

Auditing Python dependencies with pip-audit and Safety

A hands-on pip-audit tutorial covering Python dependency scanning, Safety CLI comparisons, and a workflow for catching PyPI vulnerabilities before release.

Aman Khan
AppSec Engineer
7 min read

Open source Python packages make up the bulk of most application codebases, and every one of them is a potential entry point for a supply chain attack. A single vulnerable transitive dependency — buried three levels deep in a requirements.txt you haven't looked at in months — can be enough to compromise a production system. This pip-audit tutorial walks through a practical, repeatable process for finding and fixing those vulnerabilities before they ship. By the end, you'll know how to run pip-audit and Safety CLI side by side, interpret their findings, remediate vulnerable packages, and wire the whole process into CI so that every pull request gets an automatic pypi vulnerability check. No prior security tooling experience required — just a Python project and a terminal.

Step 1: pip-audit Tutorial Basics — Install and Run Your First Scan

pip-audit is the official PyPA tool for auditing Python environments against the Python Packaging Advisory Database (which aggregates OSV and PyPI advisory data). It's the natural starting point for python dependency scanning because it understands requirements.txt, pyproject.toml, and installed environments natively.

Install it in a virtual environment so it doesn't pollute your project's dependency tree:

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

Run a scan against your currently installed environment:

pip-audit

Or scan a requirements file directly, without installing anything:

pip-audit -r requirements.txt

A clean run prints "No known vulnerabilities found." A run with issues prints a table of package name, installed version, the vulnerability ID (usually a PYSEC- or GHSA- identifier), and the fixed version. Keep this baseline output — you'll compare against it after remediation.

Step 2: Read and Prioritize the Findings

Not every finding deserves the same urgency. Before touching any code, generate a machine-readable report so you can triage systematically instead of reacting to the first line in the terminal:

pip-audit -r requirements.txt -f json -o audit-report.json

Open the JSON and sort by two things: whether the package is a direct or transitive dependency, and whether the vulnerability has a known exploit path (check the linked advisory for CVSS score and exploit maturity). A high-severity CVE in a transitive dependency you never call directly is lower priority than a moderate one in a package that parses untrusted input, like a YAML loader or an HTTP client. pip-audit doesn't rank by exploitability on its own, so this manual pass — or a policy engine layered on top — is what turns a flat list of CVEs into an actual remediation plan.

Step 3: Cross-Check with Safety CLI for Python

pip-audit and Safety CLI pull from overlapping but not identical data sources, and each has caught issues the other missed in practice. Running both gives you meaningfully better coverage than either alone, which is why most teams doing serious python dependency scanning use them together rather than picking one.

Install and run Safety CLI python tooling alongside pip-audit:

pip install safety
safety scan -r requirements.txt

Safety's free tier uses a curated vulnerability database and is particularly good at flagging malicious or typosquatted packages in addition to CVEs. If you're on Safety's commercial tier, safety scan also checks license compliance, which is worth enabling if your org has open source license policies. Diff the two tools' output: anything flagged by both is a strong remediation priority; anything flagged by only one is still worth a look, but confirm it isn't a false positive by checking the advisory source directly.

Step 4: Remediate Vulnerable Packages

For most findings, remediation is a version bump. pip-audit can even do this for you:

pip-audit -r requirements.txt --fix

This upgrades vulnerable packages to the earliest safe version pip-audit knows about, minimizing the risk of pulling in unrelated breaking changes. Always run your test suite immediately after:

pip install -r requirements.txt
pytest

If no fixed version exists yet, or upgrading breaks compatibility, you have three fallback options: pin to a patched fork, add a temporary mitigating control (input validation, network egress restriction, WAF rule) and track the CVE for follow-up, or remove the dependency if it's lightly used. Whatever you choose, document it — an unresolved finding with no rationale attached is exactly what auditors and future you will flag during a SOC 2 review.

Step 5: Automate Dependency Scanning in CI/CD

Manual scans catch what's already in your repo today; automation catches what a teammate adds tomorrow. Add pip-audit as a required check in your GitHub Actions pipeline:

name: dependency-audit
on: [pull_request]
jobs:
  pip-audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - run: pip install pip-audit
      - run: pip-audit -r requirements.txt --fix=false

Set the job as a required status check on your default branch so a PR introducing a vulnerable package can't merge silently. For faster local feedback, add pip-audit as a pre-commit hook too:

repos:
  - repo: https://github.com/pypa/pip-audit
    rev: v2.7.3
    hooks:
      - id: pip-audit
        args: ["-r", "requirements.txt"]

Running the scan at both the commit and PR stage means a vulnerable dependency has to slip past two independent checkpoints before it reaches main — and it gives developers a fast, local pypi vulnerability check instead of waiting for CI to fail.

Troubleshooting and Verification

pip-audit hangs or times out on a large environment. It's querying an advisory API per package. Use --cache-dir to persist results between runs, or scan a requirements file (-r) instead of a live environment to reduce the package count it needs to resolve.

"No known vulnerabilities" but you expected findings. Confirm you're scanning the right file — a stale requirements.txt that doesn't match your actual installed environment (common with Poetry or Pipenv projects) will produce false negatives. Export a fresh lockfile-derived requirements file before scanning: pip freeze > requirements.txt or poetry export -f requirements.txt -o requirements.txt.

pip-audit and Safety disagree on a package. This is normal — verify against the primary source. Check the package's page on osv.dev or the GitHub Security Advisory database directly to see which tool's data is current; advisory databases update on different cadences.

CI check passes locally but fails in the pipeline. Version-pin pip-audit in CI to match your local install (pip install pip-audit==2.7.3) — advisory-database differences between versions can produce different results on the same day.

Verify the whole pipeline works end-to-end by intentionally testing against a package with a known CVE, such as an old version of requests or pyyaml, in a scratch virtual environment. Both pip-audit and Safety should flag it, and your CI job should fail the build — confirming your gate actually blocks what it's supposed to before you rely on it in production.

How Safeguard Helps

pip-audit and Safety CLI are excellent for point-in-time scans, but most engineering teams run dozens of Python services, each with its own dependency tree drifting independently. Safeguard extends this same python dependency scanning discipline across your entire software supply chain — continuously, not just at commit time.

Instead of relying on developers to remember to run a scan or on a single CI job to catch everything, Safeguard aggregates dependency and vulnerability data across every repository, correlates findings with actual exploitability and reachability in your codebase, and prioritizes remediation based on real risk rather than raw CVE count. It also tracks SBOM drift over time, so you can answer "were we ever exposed to this CVE, and for how long" during an audit or incident review — something a one-off pip-audit run can't tell you.

For teams under SOC 2 or similar compliance frameworks, Safeguard turns the manual documentation step from Step 4 above — recording why a finding was accepted, deferred, or fixed — into an auditable, timestamped record automatically, so evidence collection stops being a quarterly scramble. If you're already running pip-audit and Safety in CI, Safeguard sits on top of that workflow rather than replacing it, giving you the organization-wide visibility that individual scans, run repo by repo, can't provide on their own.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.