Loading...
Skip to main content
Ecosystem Guide

What pip-audit Misses: EPSS-Aware Vulnerability Prioritization for Python

Sudhir P.
Last updated
11 min read

pip-audit finds known vulnerabilities in your Python dependencies but shows no severity, exploit-probability or CISA KEV column, so every finding looks equally urgent. On a five-package requirements file it reports 79 advisories, and sorting them by severity buries the one CISA has confirmed is exploited in the wild, a High-rated libwebp flaw in Pillow, behind seven Criticals. EPSS and KEV put it first.

Run pip-audit against the five pinned packages in the requirements file below and every finding comes back as a package, a version, an advisory ID and a fix version — nothing that says which to patch first. The sections below sort that output the way most teams do, by severity, and then re-sort it by what is actually being exploited. The two orders disagree about the most important finding.

pip-audit is a solid tool. It was created by Trail of Bits with support from Google and is maintained under the Python Packaging Authority, checks the PyPI advisory data by default (or OSV), and integrates cleanly into CI. The gap isn't detection — pip-audit finds the CVEs. The gap is prioritization: its output carries no severity, exploit-probability or KEV signal, and the severity you'd look up yourself measures theoretical impact, not the likelihood of real-world exploitation.

This piece shows the three things pip-audit misses (exploit probability, an EPSS or KEV lookup for advisories that have no CVE, and KEV status), demonstrates the gap with a reproducible fixture, and shows how to close each one. For the broader context on how dependency vulnerability scanning works beyond pip-audit specifically, see our overview guide.

What We'll Cover

What pip-audit Actually Does

When you run pip-audit, the tool:

  1. Resolves your installed packages or parses a requirements file
  2. Queries a vulnerability service — PyPI's by default, or OSV — for matching advisories
  3. Prints each finding's package, version, advisory ID and fix versions — with no severity score

The output is accurate. If pip-audit says Pillow 9.0.0 has a vulnerability, it does. The problem is the triage layer: every finding looks equally urgent unless you look up its severity yourself — and severity is a poor proxy for real-world risk.

pip-audit does not provide:

  • EPSS scores — the probability this CVE will be exploited within 30 days
  • CISA KEV status — whether this CVE is being actively exploited right now
  • EPSS or KEV for advisories with no CVE — pip-audit lists them, but EPSS and KEV are keyed by CVE ID, so nothing can enrich an advisory that has none

The Evidence: Same Requirements, Different Priority Order

This is the fixture behind the numbers below — committed to tests/fixtures/dependencies/python/vulnerable/requirements.txt so you can reproduce them:

Pillow==9.0.0
urllib3==1.26.4
cryptography==36.0.0
Django==3.2.12
requests==2.27.1

Running pip-audit 2.9.0 against it (October 4, 2026; --no-deps because every package is pinned):

$ pip-audit --no-deps -r requirements.txt
Found 79 known vulnerabilities in 5 packages
Name         Version ID                  Fix Versions
------------ ------- ------------------- -------------------
pillow       9.0.0   PYSEC-2022-168      9.0.1
pillow       9.0.0   PYSEC-2022-42979    9.2.0
pillow       9.0.0   PYSEC-2023-175      10.0.1
...
pillow       9.0.0   PYSEC-2026-1794     10.0.1
...
django       3.2.12  PYSEC-2022-213      3.2.14,4.0.6
django       3.2.12  PYSEC-2023-13       3.2.18,4.0.10,4.1.7
...

pip-audit finds all 79 — 23 in Pillow, 27 in Django, 14 in cryptography, 11 in urllib3 and 4 in requests — and leaves the ordering to you. Sort by GitHub's severity rating, as a severity-based triage would, and you get 7 Critical, 41 High, 22 Moderate and 8 Low findings (one is unrated — Pillow's own advisory for the exploited libwebp flaw below). Four of those seven Criticals have an EPSS below 10%.

Add EPSS and CISA KEV (values as of October 4, 2026) and the top of the list changes. Here are the top four by KEV status, then EPSS:

FindingCISA KEVEPSSSeverity
Pillow — CVE-2023-4863, heap overflow in bundled libwebpYes (added Sep 13, 2023)99.98%High
Django — CVE-2022-34265, SQL injection in Trunc()/Extract()No73.6%Critical
Django — CVE-2023-24580, resource exhaustionNo62.6%High
cryptography — CVE-2023-0286, vulnerable OpenSSL in the wheelsNo59.5%High

The only finding confirmed exploited in the wild is rated High, not Critical, so a severity sort puts it behind all seven Criticals. Meanwhile two of those Criticals — Pillow's CVE-2022-22817 and Django's CVE-2022-28347 — sit at an EPSS of about 3%.

EPSS Score Distribution across approximately 230,000 CVEs. The vast majority — about 85% — score below 0.1, indicating low exploitation risk. About 11% score between 0.1 and 0.5 (watch tier). Only about 4% score above 0.5, representing the highest-priority vulnerabilities that are actively or likely to be exploited.
EPSS Score Distribution: 85% of CVEs score below 0.1, 11% between 0.1 and 0.5, 4% above 0.5.0%10%20%30%40%50%60%% of CVEs0.0–0.010.01–0.050.05–0.10.1–0.20.2–0.30.3–0.40.4–0.50.5–0.60.6–0.70.7–0.80.8–0.90.9–1.0EPSS Score Range
~85% score below 0.1 — low exploitation risk, deprioritize
~11% score 0.1–0.5 — watch, patch next sprint
~4% score above 0.5 — patch first, likely exploited in the wild

EPSS Score Distribution across ~230,000 CVEs. About 85% score below 0.1 — the noise floor that makes EPSS-based triage possible.

Miss #1: No Exploitation Probability

CVSS scores measure what a vulnerability could do if exploited. A CVSS 9.8 means: if an attacker successfully exploits this, the impact is severe. It says nothing about how likely exploitation is.

EPSS (Exploit Prediction Scoring System) measures the probability that a CVE will be exploited in the next 30 days, based on threat intelligence, proof-of-concept availability, and historical exploitation patterns. An EPSS of 0.15 means there's a 15% chance of exploitation this month — roughly 7x higher than 0.02.

For four findings from the fixture above (EPSS as of October 4, 2026):

FindingCVSSSeverityEPSSDeadline (3-Signal ladder)
Pillow CVE-2022-228179.8Critical3.3%Next sprint (CVSS-critical, runtime code)
Django CVE-2022-283479.8Critical2.9%Next sprint (CVSS-critical, runtime code)
Django CVE-2022-342659.8Critical73.6%Within 1 week
cryptography CVE-2023-02867.4High59.5%Within 2 weeks

Three findings share a CVSS 9.8 and only one of them is likely to be exploited. The two under 10% EPSS are CVSS-critical findings in runtime packages, so the ladder's exception applies: they go in the next sprint rather than the quarterly cycle, behind the two findings above them. pip-audit cannot produce this table — its output has no severity, EPSS or KEV column at all. You have to layer EPSS separately, or use a scanner that includes it.

Practical rule: At most about 6% of CVEs are ever exploited, and stricter datasets put it nearer 1–2%. CVSS does not predict which ones. EPSS does.

Miss #2: Advisories With No CVE Get No EPSS or KEV Lookup

pip-audit queries PyPI's advisory service by default, or OSV. Both are well-maintained, and pip-audit lists every advisory they return, including the ones that have no CVE ID. The catch comes next: EPSS and KEV are both keyed by CVE ID, so an advisory with no CVE cannot be enriched by either.

The fixture shows it. pip-audit lists Pillow's own advisory PYSEC-2023-175 (the libwebp flaw again), which carries no CVE alias and no severity. EPSS and KEV have nothing to look up for that row, so you would have to match it to its CVE-keyed twin, PYSEC-2026-1794, by hand to see that it is the exploited flaw.

This is not rare: 5.6% of PyPI vulnerability advisories carry no CVE alias at all (excluding the malware and typosquat reports, which can never have one). A workflow keyed on CVE IDs never enriches those findings, no matter how long you wait.

The more intuitive worry is a different one: that advisories trail NVD, leaving a window where pip-audit says "clean" and NVD already knows better. We measured it, and that worry is mostly wrong. Across every CVE NVD published in 2025, joined against OSV's full PyPI export, the median gap between NVD publication and the matching PyPI advisory is 0.00 days (same day), and in 28.6% of matched cases the advisory landed before the CVE record (n=738). Latency is not the blind spot; coverage is.

Miss #3: No CISA KEV Flag

The CISA Known Exploited Vulnerabilities catalog is a curated list of CVEs confirmed to be actively exploited in the wild. Federal civilian agencies must remediate each KEV entry by a CISA-set due date — usually within three weeks of it being added, sometimes within days. Private organizations treat KEV as the clearest available signal that a vulnerability has crossed from theoretical to operational.

pip-audit does not indicate KEV status. A CVE on the KEV catalog looks identical to a theoretical one in pip-audit's output — same ID, same fix version. Your team has to look it up separately.

One finding in the fixture is on KEV: the libwebp heap overflow in Pillow 9.0.0 (CVE-2023-4863), which needs emergency patching regardless of its High rating. pip-audit lists it twice, as PYSEC-2026-1794 and as PYSEC-2023-175 (the no-CVE row from Miss #2), and neither row marks it as exploited. You only find out by cross-referencing the CVE against the catalog yourself.

Decision list with full signals (the ladder from the 3-Signal triage guide, applied to pip-audit output):

  • On CISA KEV: patch within 24 hours. No exceptions.
  • Not on KEV, so check the EPSS score:
    • Above 90%: patch within 24 hours.
    • 70–90%: within 1 week.
    • 30–70%: within 2 weeks.
    • 10–30%: within 30 days.
    • Below 10%: log and monitor (routine maintenance), except CVSS-critical findings in runtime code, which go in the next sprint.

pip-audit stops before the first question — it carries no KEV or EPSS data. The decision list above needs both.

A Practical Python Security Workflow

The recommended stack combines pip-audit's strengths (fast, zero-config, PyPA-native) with signals it lacks:

In CI (every build):

# Fast check — catches advisories at merge time
pip-audit -r requirements.txt --format=json --output=audit.json

Weekly or on-demand (cross-ecosystem with EPSS + KEV):

Run the same requirements.txt through a scanner that includes EPSS and CISA KEV enrichment. GeekWala's PyPI scanner accepts requirements files directly and ranks CISA KEV findings first, then findings with an EPSS above 10%, rather than sorting by CVSS. How to scan requirements.txt online walks through the paste-and-scan flow.

On new CVE disclosure:

Subscribing to PyPA security announcements is still worth doing, but not because of a latency gap — the measured gap is essentially zero. It's worth doing because a meaningful share of advisories never acquire a CVE at all, and CVE-keyed enrichment (EPSS, KEV) cannot score those by construction.

pip-audit in CI is table stakes. The EPSS layer is what separates a noise-generating scanner from an actionable one.

Frequently Asked Questions

Quick answers to the questions teams ask most often about pip-audit.

Is pip-audit still worth using?

Yes. pip-audit is fast, maintained, and has no false positives from CPE matching (unlike OWASP Dependency-Check). It integrates cleanly into GitHub Actions with a single action. The limitation is prioritization, not detection.

Use pip-audit in CI for immediate merge-time feedback. Use EPSS-enriched scanning for triage decisions. They're complementary, not competing. See the full feature-by-feature pip-audit alternative comparison for the complete breakdown, including ecosystem coverage and pricing.

Does pip-audit cover transitive dependencies?

pip-audit scans your full installed environment when invoked without a requirements file (pip-audit alone), which includes transitive packages. With -r requirements.txt, it resolves transitives using pip's resolver. The PyPA advisory database covers direct and indirect dependencies equally — if urllib3 is a transitive dependency of requests, pip-audit catches it.

The limitation isn't coverage; it's still prioritization. In pip-audit's output, a transitive dependency with a 60% EPSS looks exactly like a direct one at 1%.

What's the difference between pip-audit and safety?

Both scan Python dependencies for known CVEs. pip-audit uses PyPI's advisory service by default, or OSV — both open and free. safety (by PyUP) historically used a separate commercial database with earlier advisory publication; some advisories appeared in safety's database before OSV/PyPA. The gap has narrowed as PyPA advisory coverage improved. Neither tool provides EPSS or KEV enrichment.

How often should I run pip-audit?

Run it on every CI build that installs dependencies (including PRs and scheduled builds against your main branch). Vulnerabilities are disclosed continuously — a package clean yesterday may have a new CVE today. The nightly or scheduled scan pattern — even just a cron job running pip-audit against your production lockfile — catches disclosures between deploys.

Can pip-audit replace a dedicated SCA tool?

For small Python-only projects: pip-audit handles detection adequately. The gap is prioritization — you'll still need EPSS context for triage. For multi-ecosystem projects (Python + npm, Python + Go, etc.), a cross-ecosystem scanner with a unified dashboard becomes essential. Switching between pip-audit, npm audit, and go vulnerability checks on every triage session is operationally expensive.


See the EPSS signal pip-audit can't show you.

Scan your requirements.txt with EPSS + CISA KEV enrichment → — paste your requirements file or upload a poetry.lock or Pipfile.lock. Results in under 60 seconds, no account needed for up to 10 packages.

See the feature-by-feature comparison

This article is GeekWala's technical deep-dive for GeekWala vs pip-audit — pricing, feature matrix, and CTA live there.