pip-audit
A command line scanner that takes the Python packages you have installed, or the ones a requirements file resolves to, and checks each name and version against a vulnerability feed. The default feed is the PyPI JSON API backed by the Python Packaging Advisory Database; OSV is available with a flag. Output comes as a table, Markdown, JSON, or a CycloneDX SBOM, and --fix will upgrade the affected packages in place. It lives under the pypa organisation and is maintained in part by Trail of Bits with support from Google.
The default choice for checking Python dependencies against published advisories: pypa-maintained, no account needed, and it drops into CI in one line. Go in knowing it only compares names and versions against a feed, so a clean run means no advisory matched, not that your dependencies are safe.
Use it if
- You want a CI gate that fails the build when a dependency has a published advisory, since the exit code is 1 whenever anything is found and cannot be suppressed from inside the tool
- You need a CycloneDX SBOM out of the same run that does the audit, in JSON or XML, without adding a second tool
- You already pin and hash your requirements, because --require-hashes and --no-deps then skip resolution entirely and the audit finishes in seconds
- You prefer the advisory data that PyPI itself serves, rather than a vendor database that needs an account or an API token
- You expected it to find vulnerable code: it reads dependency metadata, not your source. A package with a known advisory is flagged whether or not you call the affected function, and an exploitable bug in your own code is invisible to it
- Your project is not pip-shaped: Poetry, PDM, pipenv and uv lock files are not read directly. Only pyproject.toml and pylock.*.toml are recognised as project inputs, so everything else needs an export step first
- You need vulnerabilities in the non-Python layer. The README says outright that advisories for a vulnerable shared library behind a Python wheel are unlikely to appear, because the wheel version is not tied to the C library version
- You want per-finding suppression with an expiry date and a reason. There is only --ignore-vuln ID repeated on the command line, with no config file and no time limit, so ignores silently outlive the reason for them
- You are auditing untrusted input: the security model states that pip-audit -r INPUT is functionally equivalent to pip install -r INPUT, arbitrary setup code included, so it is not a safe way to inspect a hostile requirements file
- You need it to be fast on an unpinned requirements file. Without hashes or --no-deps it performs its own resolution, which costs roughly what a pip install of the project costs
Setup reality
pip install pip-audit needs Python 3.10 or newer and pulls a fairly wide tree of its own (rich, requests, CacheControl, cyclonedx-python-lib, pip-api, pip-requirements-parser), so install it in its own environment or with a tool runner rather than next to the code you are auditing. Decide early which mode you are in, because the modes behave very differently: bare pip-audit audits the environment you are standing in, pip-audit -l narrows that to non-system packages, pip-audit -r requirements.txt resolves the file in a temporary virtualenv, and pip-audit . reads a local project. Resolution is the slow part and the part that runs arbitrary package code, so pinned plus hashed inputs with --require-hashes are both faster and safer. The exit code is deliberately not configurable: 0 for clean, 1 for anything found, and the documented workaround for a soft check is a shell || true. There is no ignore file, so every accepted finding becomes another --ignore-vuln flag in your CI config that nobody remembers to remove. Private indexes work through --index-url and --extra-index-url but interactive authentication does not, keyring only works through the subprocess provider, and some registries need a hardcoded username. Expect the first run against a mature project to produce findings you cannot act on, because the feeds carry advisories with no fix version; filtering those out means post-processing the JSON output yourself.
Patterns
Scan the environment you are inaudit-current-environment
pip-audit
# only packages installed in this virtualenv, not system site-packages
pip-audit --localBare pip-audit audits whatever interpreter is on PATH. Inside a container that often includes distro-provided packages, which is what --local filters out.
Audit a requirements fileaudit-requirements-file
pip-audit -r requirements.txt
# multiple files in one run
pip-audit -r requirements.txt -r requirements-dev.txtThis resolves the file in a temporary environment, which executes package build code. Treat it with the same trust you would give pip install -r on the same file.
Make a pinned audit fastskip-dependency-resolution
# fails if anything is not pinned to an exact version
pip-audit --no-deps -r requirements.txt
# stricter: also requires --hash entries
pip-audit --require-hashes -r requirements.txtBoth skip resolution, which is where nearly all the runtime goes. --require-hashes is the better default because it also checks integrity.
Gate a CI job on the exit codefail-ci-build
- name: Audit dependencies
run: pip-audit -r requirements.txt --require-hashes
# or use the official action
- uses: pypa/gh-action-pip-audit@v1.1.0
with:
inputs: requirements.txtExit code 1 means findings, 0 means none, and there is no flag to soften it. For a warn-only job append || true and read the output instead.
Suppress a specific advisoryignore-known-finding
pip-audit --ignore-vuln GHSA-w596-4wvx-j9j6
# repeat the flag for each one
pip-audit --ignore-vuln PYSEC-2023-100 --ignore-vuln CVE-2024-12345Aliases work, so a GHSA or CVE id is accepted where the feed only has a PYSEC id. There is no expiry and no place to record why, so review the list on a schedule.
Get JSON for downstream processingemit-machine-readable
pip-audit -r requirements.txt -f json -o audit.json
# only fail when a fix actually exists
test -z "$(pip-audit -r requirements.txt -f json 2>/dev/null \
| jq '.dependencies[].vulns[].fix_versions[]')"Descriptions and aliases default to on for the json format, so the file is large. The jq form is the documented way to ignore findings with no released fix.
Produce a CycloneDX SBOMgenerate-sbom
pip-audit -r requirements.txt -f cyclonedx-json -o sbom.json
pip-audit -r requirements.txt -f cyclonedx-xml -o sbom.xmlThe --desc and --aliases flags have no effect on either CycloneDX format. The exit code still reflects findings, so an SBOM run can fail your build.
Audit against OSV instead of PyPIswitch-vulnerability-service
pip-audit -s osv -r requirements.txt
# self-hosted or mirrored OSV endpoint
pip-audit -s osv --osv-url https://osv.internal.example/v1/queryThe two feeds do not report identically. Running both and comparing is a reasonable one-off exercise before you commit to one in CI.
Audit a project or its lock filesaudit-project-directory
pip-audit .
# read pylock.*.toml instead of resolving pyproject.toml
pip-audit --locked .Only pyproject.toml and pylock.*.toml are recognised. Poetry, pipenv and uv projects need an export to requirements format first.
Upgrade affected packagesauto-upgrade-vulnerable
# see what would change without touching anything
pip-audit --fix --dry-run
pip-audit --fix--fix upgrades to the first fixed version, ignoring whether that crosses a major boundary. Always run the dry run first and re-run your test suite after.
Wire it into pre-commitrun-via-pre-commit
- repo: https://github.com/pypa/pip-audit
rev: v2.10.1
hooks:
- id: pip-audit
args: ["-r", "requirements.txt"]
ci:
skip: [pip-audit]The ci.skip entry is needed because pre-commit.ci blocks network calls and the hook cannot reach the advisory feed there.
Point at an internal package indexaudit-private-index
pip-audit \
--index-url https://pypi.internal.example/simple \
--extra-index-url https://pypi.org/simple \
-r requirements.txtThere is no interactive prompt for credentials. Keyring works only through the subprocess provider, and registries such as Google Artifact Registry need their fixed username supplied.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| safety | PyPI | You want a commercial vulnerability database with policy files, expiring ignores and a hosted dashboard behind it |
| bandit | PyPI | You need static analysis of your own Python code for insecure patterns, which is the gap pip-audit deliberately does not cover |
| cyclonedx-bom | PyPI | SBOM generation is the actual goal and vulnerability matching happens later in a separate scanner |