How to Scan Python Dependencies for Vulnerabilities
Every dependency in a Python project is a potential source of known security vulnerabilities. Scanning those dependencies against a vulnerability database catches problems before they reach production. Vulnerabilities in dependencies are one vector in Python supply chain attacks; running uv audit regularly is a layered defense that closes that specific attack surface.
Using uv audit
uv includes the uv audit command, which checks project dependencies against the OSV (Open Source Vulnerabilities) database.
Run it from the root of a uv project:
$ uv audit
Resolved 6 packages in 1ms
Found 30 known vulnerabilities and no adverse project statuses in 5 packages
Vulnerabilities:
idna 2.10 has 4 known vulnerabilities:
- GHSA-65pc-fj4g-8rjx: Internationalized Domain Names in Applications (IDNA): Specially crafted inputs to idna.encode() can bypass CVE-2024-3651 fix
Fixed in: 3.15
Advisory information: https://nvd.nist.gov/vuln/detail/CVE-2026-45409
...
uv audit resolves the project (locking it first if uv.lock is missing or stale) and queries OSV for known vulnerabilities in each dependency. When vulnerabilities are found, it prints each one with the fixed version and a link to the advisory and exits with a non-zero status code. When no vulnerabilities are found, it exits with status 0.
To emit the findings as JSON for scripts or dashboards, pass --output-format json. Pass --output-format sarif to upload the results to GitHub code scanning.
To point uv audit at a custom vulnerability service instead of OSV, use the --service-url and --service-format flags:
$ uv audit --service-url https://vuln.example.com/api --service-format osv
Note
uv audit is in preview, and each run prints a warning that its interface may change. Pass --preview-features audit-command to silence it.
Ignoring a vulnerability
Not every finding is actionable: a fix may not exist yet, or the vulnerable code path may be unreachable from the project. Suppress a finding by passing its advisory ID. --ignore suppresses it unconditionally; --ignore-until-fixed suppresses it only while no fixed release exists, so the audit fails again once a patch is published.
$ uv audit --ignore CVE-2021-33503 --ignore-until-fixed GHSA-2xpw-w6gg-jr37
Both flags can be repeated, and both accept any alias for the advisory (CVE, GHSA, PYSEC, or OSV IDs). Ignoring by an alias suppresses every advisory record that shares it.
To persist ignores so CI runs apply them too, add a [tool.uv.audit] section:
# pyproject.toml
[tool.uv.audit]
ignore = ["CVE-2021-33503"]
ignore-until-fixed = ["GHSA-2xpw-w6gg-jr37"]If an ignored ID matches no finding in the project, uv audit prints a warning, which catches typos and entries that have outlived the vulnerable dependency.
Catching deprecated and archived dependencies
uv audit also reports adverse project statuses. PyPI’s project status markers (defined by PEP 792) let maintainers mark a project as archived (no further releases expected), deprecated (obsolete by its maintainers’ own judgment), or quarantined (locked by PyPI admins as unsafe). Dependencies carrying one of these statuses appear in the audit report:
$ uv audit
Resolved 2 packages in 1ms
Found no known vulnerabilities and 1 adverse project status in 1 package
Adverse statuses:
- pathlib is archived
An archived or deprecated dependency still installs and runs, but it will not receive security fixes. Treat the finding as a prompt to plan a replacement. Adverse statuses alone do not change the exit code: a project with an archived dependency and no vulnerabilities still exits 0, so grep the output if CI should fail on them.
Auditing a self-contained script
To audit a single-file script with PEP 723 inline metadata instead of a project, pass --script:
$ uv audit --script demo.py
uv resolves the script’s declared dependencies and checks them against the same vulnerability database. No uv.lock is required. A script without a requires-python field triggers a warning that uv picked a default Python version for the resolution; add the field to the metadata block to silence it.
Using pip-audit
pip-audit is an established alternative maintained by the PyPA. It queries PyPI’s advisory database by default (pass -s osv to query OSV instead) and works with any Python project, regardless of whether the project uses uv.
pip-audit does not read uv.lock, and running it bare through uvx audits pip-audit’s own ephemeral environment rather than the project. Export the lockfile first, then point pip-audit at the export:
$ uv export -o requirements.txt
$ uvx pip-audit -r requirements.txt
Found 21 known vulnerabilities in 3 packages
Name Version ID Fix Versions
-------- ------- --------------- -------------
idna 2.10 PYSEC-2024-60 3.7
...
Like uv audit, pip-audit exits with a non-zero status code when vulnerabilities are found. The two tools consult different databases, so their counts differ on the same project.
Choosing between uv audit, pip-audit, and Dependabot
uv audit and pip-audit are gates: they run on demand or in CI and fail the build. GitHub’s Dependabot alerts are monitoring: GitHub scans uv.lock in the repository on its own schedule and opens alerts and fix pull requests without a CI step. Use a gate to block a vulnerable dependency from merging and Dependabot to learn about advisories published after the last CI run. When a Dependabot ignore rule is needed, keep it from blocking security updates.
None of these tools block known-malicious packages at install time. That is a separate uv setting in the same [tool.uv.audit] table; protecting against supply chain attacks with uv covers it.
Adding vulnerability scanning to CI
uv audit works well in GitHub Actions because it returns a non-zero exit code on findings, which fails the CI step. Pass --locked so the job also fails when uv.lock is out of date instead of silently re-resolving:
name: Vulnerability scan
on: [push, pull_request]
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7.0.1
- uses: astral-sh/setup-uv@v10.0.1
- run: uv audit --lockedTo use pip-audit instead (for example, if the project does not use uv), replace the last step with uv export -o requirements.txt && uvx pip-audit -r requirements.txt.