How to Audit Python Dependencies for Known Vulnerabilities
How to Audit Python Dependencies for Known Vulnerabilities
Every Python project is mostly other people's code. Add a web framework and a database driver and you have pulled in dozens of packages, most of which nobody on your team will ever read. That is how the ecosystem is supposed to work. It also means your dependency list is an attack surface you did not write and do not control.
The good news: auditing it takes half an hour the first time and a few minutes a month after that. The tools are open source, the advisory data is public, and most fixes are a version bump. But the difference between a quick check and a real audit is knowing what you are actually shipping, reading the results critically, and making the fix stick. Here is how to do all three.
Start with an honest inventory
A scanner is only as good as the list you hand it. A short requirements.txt with a few loose pins is not that list. What you actually ship is the fully resolved set of packages — direct dependencies plus everything they dragged in — and that is where most findings hide.
Build the list the same way you build the application. Create a clean virtual environment, install from your declared files, and freeze what actually landed:
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
pip freeze > requirements.lock
If you already generate a lockfile with pip-tools, Poetry, PDM or uv, scan that instead. It is exact, it is what your deployment installs, and it does not depend on anyone's laptop being tidy. A lockfile also gives you a place to record the resolved versions of transitive dependencies, which are often the ones with the nastiest CVEs.
To find out who pulled in a package you have never heard of, pipdeptree prints the dependency tree and shows which parent requires what. That context matters later, when you have to decide whether a vulnerable package is something you can simply remove. For example, if a vulnerable package is only required by a development tool you rarely use, you might drop it entirely. If it is required by your web framework, you will need a different strategy.
Pick a scanner, and consider running two
Several open source scanners cover Python well, and they draw on overlapping advisory sources: the PyPA advisory database, OSV, the GitHub Advisory Database and the NVD. They disagree at the edges, which is a good reason to run two cheap ones rather than agonise over which is best.
- pip-audit — maintained by the Python Packaging Authority. Reads requirements files, lockfiles and installed environments, reports against PyPA and OSV data, and can attempt upgrades with its fix option. Run pip-audit -r requirements.txt for a quick check, or pip-audit --fix to apply safe upgrades automatically.
- osv-scanner — Google's client for the OSV database. Point it at a directory or a lockfile; it handles many ecosystems, which helps if your repository also contains Node or Go code. A single command like osv-scanner --lockfile=requirements.lock covers all of them.
- Safety — the long-running Python CLI. Command names have changed between releases, so check the current documentation before wiring it into a script. It focuses on Python packages and offers a paid database with additional advisories.
- Trivy or Grype — container and filesystem scanners that also read Python lockfiles. A good fit when the Docker image is the artifact you really ship. Trivy, for instance, can scan a requirements file or an entire container image in one pass.
- Dependabot — not a local scanner, but GitHub's alerts and pull requests for vulnerable dependencies turn findings into reviewable diffs. Configure it in your repository settings and let it open PRs when a patched version appears.
A reasonable starting point: run pip-audit against your requirements file in a terminal today, and add a second scanner in CI later if you want a second opinion. pip-audit exits with a non-zero status when it finds something, which is exactly what you want from a pipeline step. If you use multiple scanners, compare their outputs; a vulnerability that only one tool reports is worth investigating but may be a false positive.
Read the report before you act
A vulnerability report is a list of leads, not a list of emergencies. Before you upgrade twelve packages at six o'clock on a Friday, work through a few questions.
- Is the package installed at the version reported? Version-range matching produces false positives, especially for packages installed from a git URL, a private index or a vendored copy.
- Is it a runtime dependency or a development one? A linter with a known issue is a different problem from a web framework that parses untrusted input. The first can often wait; the second might need a patch today. Check whether the vulnerable package is in your production install set, not just your dev requirements.
- Is the vulnerable function actually reachable? Some advisories flag a flaw in a code path you never call. For instance, a vulnerability in a rarely used XML parser might not affect your app if you only parse JSON. That does not make the finding irrelevant, but it changes the urgency.
- What is the severity and exploitability? A CVSS score of 9.8 with a public exploit is a different beast from a 4.3 that requires local access. Scanners often include these details; use them to prioritise.
- Is there a patch available? If a fixed version exists, an upgrade is usually the fastest path. If not, you may need a workaround, a configuration change, or a decision to accept the risk temporarily.
Only after answering these questions should you start changing versions. A calm triage saves you from breaking your application in a panic.
Triage and remediation strategies
Once you have a shortlist of real issues, you need a plan. The best-case scenario is a straightforward upgrade: a new version of the vulnerable package fixes the flaw and does not break your code. Tools like pip-audit can suggest these upgrades, but always run your test suite afterward.
If no patch exists, consider these options in order:
- Remove the dependency. If the package is only used in a small utility, can you replace it with a standard library function or a different, safer package?
- Apply a workaround. Sometimes you can disable the vulnerable feature, add input validation, or use a configuration option that closes the hole.
- Pin to a safe version. If a newer version introduced the vulnerability, you might pin back to an older, unaffected release. This is a temporary measure; document it and set a reminder to revisit.
- Accept the risk. If the vulnerable code is not reachable or the impact is negligible, you may choose to document the decision and move on. Just make sure the decision is deliberate, not accidental.
For teams with many projects, a small script that runs pip-audit and posts results to a Slack channel or a tracking issue can keep the work visible without turning it into a weekly fire drill.
Automate the audit in CI
Manual audits are great for the first pass, but vulnerabilities are disclosed continuously. The only way to stay ahead is to run a scanner on a schedule and on every pull request. Most CI systems make this easy.
In GitHub Actions, for example, you can add a step that installs pip-audit and runs it against your lockfile. If it finds a vulnerability, the job fails and the pull request is blocked. For a scheduled scan, use a cron trigger to run the same command every week and notify your team of any new findings.
You can also enable Dependabot security alerts and automated security updates. Dependabot will open a pull request when a patched version is available, and you can review the diff just like any other change. That turns a scary advisory into a routine code review.
Finally, keep your dependency list small. Every package you remove is one less thing to audit. Before adding a new library, ask whether you really need it or whether the standard library plus a few lines of code would do. A lean dependency tree is not just faster to install; it is easier to defend.
Auditing Python dependencies is not glamorous work, but it is one of the highest-leverage security habits you can build. Start with an honest inventory, run a scanner or two, read the results with a critical eye, and automate the process so it does not depend on anyone's memory. Half an hour today can save you a very bad week later.
Photo: Ann H / Pexels



