How to Fix a Trivy CVE Vulnerability in requirements.txt
Trivy scans your source tree and exits non-zero on a CRITICAL CVE in a pinned dependency - the Fixed Version column in its output tells you exactly what to change. Bump the pin in requirements.txt and the gate passes.
How Trivy's filesystem scan works
trivy fs walks your project files looking for dependency manifests
(requirements.txt, go.sum, package-lock.json, etc.) and cross-references
every version against the NVD/OSV advisory database. With --exit-code 1 and
--severity CRITICAL, it returns a non-zero exit code the moment it finds a
CRITICAL-severity CVE - which is the standard CI gate pattern.
# See the full finding with the Fix Version column
trivy fs --severity CRITICAL .
# CI gate - exits 1 if any CRITICAL is found, exits 0 if clean
trivy fs --severity CRITICAL --exit-code 1 .
The output table has five columns: Library, Vulnerability, Severity, Installed Version, and Fixed Version. The Fixed Version column is the source of truth - it tells you the minimum patched release.
Reading the finding
A typical CRITICAL finding looks like this:
requirements.txt
================
Library Vulnerability Severity Installed Fixed
--------- ---------------- --------- ---------- -------
pyyaml CVE-2020-14343 CRITICAL 5.3.1 5.4
CVE-2020-14343 is an arbitrary code execution vulnerability in PyYAML's
FullLoader - it deserializes arbitrary Python objects from untrusted YAML
input. It was patched in 5.4 by restricting the default loader.
Fixing it: bump the pin
Open requirements.txt and update the vulnerable line. A range pin is fine;
an exact pin is more reproducible:
# before
pyyaml==5.3.1
# after - range is acceptable
pyyaml>=5.4
# or pin the exact patched release for full reproducibility
pyyaml==5.4.1
Re-run the gate:
trivy fs --severity CRITICAL --exit-code 1 .
# exit code 0 - no CRITICAL findings
Why this pattern matters in CI
The hard part of dependency CVE management is not the one-line fix - it is
making the gate fail CI reliably so old pins cannot drift back in. Once
trivy fs --exit-code 1 is in the pipeline:
- Any future pull request that introduces a vulnerable pin will fail before merge.
- You can add
--severity CRITICAL,HIGHto also block HIGH-severity findings as your risk tolerance tightens. - Combine with
trivy imageto also catch CVEs baked into the base OS layer of your Docker image - a cleanrequirements.txtdoes not mean a clean image.
To automate bumps, layer Renovate or Dependabot on top: they open PRs whenever a newer patched version is available, so you review and merge rather than hunt manually.
Want to try it hands-on? HeyDevJob gives you this exact setup in a live cloud workspace in your browser - edit it, run it, and see it work. Free, nothing to install.
Try it in a workspace →What you'll practice
- Running trivy fs --severity CRITICAL to read the Fixed Version column
- Bumping a vulnerable requirements.txt pin and re-running the CI gate
- Adding --exit-code 1 to trivy fs so CRITICAL CVEs fail the build
FAQ
How do I find what version fixes a CVE in Trivy's output?
Run trivy fs --severity CRITICAL . and read the Fixed Version column in the output table. That column names the minimum patched version - pin to that or any higher release.
Why does Trivy exit with a non-zero code in CI?
The --exit-code 1 flag tells Trivy to return a non-zero exit code when it finds any finding at or above the specified severity. This is the standard way to fail a CI pipeline on a CRITICAL CVE.
Is a range pin like pyyaml>=5.4 safe, or should I use an exact version?
A range pin clears the CVE gate immediately and automatically takes the next patched release. An exact pin is more reproducible. Both are valid - many teams use a range for quick unblocking and then pin exact in a follow-up PR.
Keep learning
Learn it by doing. Open this in a live cloud workspace, make the change yourself, and keep a record of the work you can share.
Open the workspace →