Vulnerability management turns an alert into a dated decision: detect the CVE, judge its real risk, set a fix deadline, fix or document, then verify. This lesson is for the DevOps engineer who receives scanner reports and must say, within 24 hours, what gets fixed first and what is accepted.
Everything is measured on 1 October 2026 on the nginx:1.30.5-trixie image:
Trivy 0.74.0 reports 390 vulnerabilities, Grype 0.119.0 classes
18 matches as Critical where Trivy counts only one, and the flaw most
likely to be exploited is rated Negligible. A raw report is therefore not
handled in the order it is displayed.
What you will learn
- Distinguish CVE, CVSS, EPSS and KEV, and know which one is authoritative for a decision
- Run a triage workflow in five reproducible steps
- Set fix deadlines per criticality, and document the exceptions
- Answer "are we affected?" quickly with an SBOM and a VEX document
- Adapt your detection to the NVD cutback since April 2026
- Protect your own scanning tools, links in the chain like any other
What is a CVE vulnerability?
A CVE (Common Vulnerabilities and Exposures) is the public identifier
assigned to a known flaw in a piece of software: CVE-2021-44228 designates
Log4Shell, in the Java library Log4j. This identifier is the common
reference between scanners, vendor bulletins and public databases: it lets
you check that two tools are talking about the same flaw.
The volume makes manual sorting impossible. On 29 September 2026, the FIRST API assigned an EPSS score to 380,526 CVEs, and the CISA KEV catalogue listed 1,730 whose exploitation is confirmed. Reading these indicators in detail is the subject of the CVE, CVSS and EPSS lesson; this one turns them into a process, from report to fix.
Severity metrics
A CVE alone does not tell you the urgency. Three public indicators complete it, each answering a different question, and a fourth, the exposure of the component on your side, belongs to you alone. The table sums up what each indicator measures and what it does not say.
| Metric | What it measures | What you need to know |
|---|---|---|
| CVSS (Common Vulnerability Scoring System) | Severity if the attack succeeds, from 0 to 10 | The score varies with the source: vendor, distribution or NVD |
| EPSS (Exploit Prediction Scoring System) | Probability of exploitation within 30 days | Model v4 published since 17 March 2025, recalculated daily: 0.73 reads 73% |
| KEV (Known Exploited Vulnerabilities) | Confirmed exploitation, maintained by CISA | Presence in the catalogue = urgent handling |
CVSS and EPSS do not replace each other. On the measured image, the flaw with
the highest EPSS is CVE-2011-3389 (BEAST, in libgnutls30t64): 73.3%,
99th percentile, while Debian rates it Negligible. Conversely, Grype's 18
Critical matches all have an EPSS below 1% and carry the wont-fix
status. FIRST sets no official threshold either: combining the indicators is
up to you.
The life cycle of a vulnerability
A published CVE only concerns you if the vulnerable component is present on your side, and ideally on a code path that is actually reached. The most often forgotten step is therefore detection in your IT estate: without an up-to-date SBOM, you cannot answer quickly. The diagram follows a vulnerability from publication to closure, with the three possible outcomes of the treatment.
The NVD is slowing down: what to change in your detection
The NVD (National Vulnerability Database), the US database run by NIST, changed its policy on 15 April 2026. CVE submissions grew by 263% between 2020 and 2025; NIST enriched nearly 42,000 of them in 2025, 45% more than any previous year, without keeping up.
Since then, only three categories are enriched as a priority (adding a CVSS score, CWE, CPE): CVEs in the KEV catalogue, those affecting software used by the US federal government, and those of critical software as defined by Executive Order 14028. Any non-enriched CVE published before 1 March 2026 moves to "Not Scheduled". NIST also no longer computes its own score when the CNA (the organisation that assigned the CVE) already provides one. Source: NIST announcement.
The practical consequence is twofold. A tool that only reads the NVD CVSS field may show a CVE without a score; and the displayed score increasingly depends on the chosen source. Two databases fill the gap: CISA's Vulnrichment programme and OSV.dev, an open database that aggregates advisories per ecosystem (npm, PyPI, Go, Maven). Prefer a scanner that cross-checks several sources, and compare two of them on your critical images.
Prerequisites
This lesson assumes a few notions already covered in the course, and a working pipeline. The pages below revisit them if needed.
- DevSecOps basics: DevSecOps fundamentals
- Reading the indicators: CVE, CVSS and EPSS
- Dependency scanning: SCA
- A CI/CD pipeline in place, with access to the image registry
Key concepts before you start
Three objects come back throughout the workflow: the inventory of what you ship, the declaration of what is not exploitable, and the tool that compares one with the CVE databases. Understanding them first avoids mixing them up in the pipeline.
The SBOM: your dependency inventory
An SBOM (Software Bill of Materials) is the inventory of what an artifact contains: packages, libraries, versions, licences, transitive dependencies. Without it, answering "am I exposed to this CVE?" means rescanning every application; with it, the answer is a single query on the components.
Two formats dominate: CycloneDX (OWASP, version 1.7 released in October 2025) and SPDX (Linux Foundation, version 3.0.1). Measured: Syft 1.52.0 produces CycloneDX 1.7 by default, and lists 152 packages in the nginx image, plus 3,236 files. Check which version the rest of your chain consumes before mandating one.
VEX: saying what is not exploitable
A VEX (Vulnerability Exploitability eXchange) is a document that declares,
for a given CVE and product, a status: not_affected, affected, fixed
or under_investigation. A not_affected must carry a justification, for
example "the vulnerable code is not on an executed path".
The benefit is measurable: with an OpenVEX document declaring
CVE-2011-3389 not exploitable in the nginx image, trivy image --vex goes from
390 to 389 vulnerabilities. The decision is written once,
versioned, and the scanner applies it on every run instead of having the
same alert reanalysed.
SCA: automated dependency scanning
SCA (Software Composition Analysis) compares the components of a project or an image with the CVE databases (NVD, OSV.dev, GitHub Advisory Database, distribution advisories). It is the first detection tool. Since the NVD cutback, no single database is enough, and the tools do not read the same ones: this explains the gaps measured below between Trivy and Grype.
The CVE vulnerability management workflow, in 5 steps
The workflow has five steps, always in the same order: detect, triage, set the deadline, treat, verify. Each one leaves a trace (report, ticket, VEX), which lets you prove afterwards that an alert was indeed handled.
Step 1, Detect: wiring up the scanning tools
Detection must be automatic and cover three families of assets: images at build time and at regular intervals, including those already in production; application dependencies on every merge request; system packages on servers. A new CVE can hit an image published three months ago: the scheduled scan (cron) on the registry matters as much as the build-time scan.
| Tool | What it scans | Integration |
|---|---|---|
| Trivy | Images, file system, repositories, IaC | CLI, image, GitHub and GitLab |
| Grype (in French) | Images, SBOM | CLI, image |
| OSV-Scanner (in French) | Multi-ecosystem dependencies via OSV.dev | CLI, GitHub Actions |
| Dependabot (in French) | Dependencies of a GitHub repository | Native to GitHub |
| Renovate (in French) | Dependencies, with automatic merge requests | Multi-platform |
The SBOM is generated at build time, then rescanned without rebuilding the
image. The two commands below were replayed on the nginx image: Syft writes
the inventory, Grype checks it against its database, and
--fail-on critical makes the command fail with code 2.
# CycloneDX inventory of the image, pinned by digestsyft nginx:1.30.5-trixie@sha256:b972f831f200b19ef0767938224f9711e74cd783718738cd7405d5cabf75c442 \ -o cyclonedx-json=sbom.json
# Scan the SBOM, without the imagegrype sbom:sbom.json --fail-on criticalecho $?# 2Step 2, Triage: assessing the real impact
Triage answers three questions, in this order: is the component exposed (internet, internal, test)? What do the KEV, EPSS and CVSS indicators say, in this order of weight? What would the business impact of an exploitation be (data leak, downtime, privilege escalation)?
The trap is to triage on the displayed severity alone. Measured on the
same image, the same flaw CVE-2026-19931 in curl is rated MEDIUM by
Trivy and Critical by Grype, which uses the NVD score. The status differs
too: affected for Trivy, wont-fix for Grype. For triage, Grype can sort by
probability of exploitation:
grype sbom:sbom.json --sort-by epssNAME INSTALLED FIXED IN TYPE VULNERABILITY SEVERITY EPSS RISKlibgnutls30t64 3.8.9-3+deb13u4 deb CVE-2011-3389 Negligible 73.3% (99th) 3.7nginx 1.30.5-1~trixie deb CVE-2009-4487 Negligible 29.5% (98th) 1.5libldap2 2.6.10+dfsg-1 deb CVE-2017-17740 Negligible 7.0% (93rd) 0.4None of these CVEs is in the KEV catalogue as of 30 September 2026. The following matrix takes up the starting policy of the CVE lesson and attaches a deadline to it. The thresholds are common benchmarks, not a FIRST standard: adapt them to your exposure.
| Situation | Indicators | Priority |
|---|---|---|
| Confirmed exploitation | listed in KEV | Critical |
| Severe and probably exploited | CVSS ≥ 9 and high EPSS | Critical |
| Severe, rarely exploited | CVSS ≥ 9, low EPSS | High |
| Medium but heavily exploited | CVSS 4 to 6, high EPSS | High |
| Low and rarely exploited | CVSS < 4, low EPSS | Low: schedule or accept |
Step 3, Applying the SLAs
A security SLA (Service Level Agreement) is an internal commitment on the maximum fix time, per priority level. Without it, CVEs pile up and every trade-off is replayed in a meeting. The values below are a starting point, the same as in the lesson on security KPIs:
| Priority | Maximum time | Escalation if exceeded |
|---|---|---|
| Critical | 24 hours | CISO and CTO notified |
| High | 7 days | Security manager and lead dev |
| Medium | 30 days | Ticket followed in sprint review |
| Low | 90 days | Security backlog, or documented acceptance |
Step 4, Fix: patch, mitigation or exception
There are three outcomes, in this order of preference. Fixing updates the dependency or the base image, through the pipeline and never by hand. Mitigating reduces exposure while waiting for a fix: disable the feature, filter with a WAF (web application firewall), isolate the component. Declaring a flaw not exploitable goes through a justified VEX document.
The two Trivy commands below separate what can be fixed from what has to wait.
Measured on the nginx image: the blocking scan reports 80 HIGH and CRITICAL
vulnerabilities; with --ignore-unfixed, only 13 remain, all HIGH, which
have a published fix. The exit code stays 1 in both cases.
# Blocking: any HIGH or CRITICAL failstrivy image --severity CRITICAL,HIGH --exit-code 1 \ nginx:1.30.5-trixie@sha256:b972f831f200b19ef0767938224f9711e74cd783718738cd7405d5cabf75c442# Total: 80 (HIGH: 79, CRITICAL: 1)
# Only what has a published fixtrivy image --severity CRITICAL,HIGH --ignore-unfixed --exit-code 1 \ nginx:1.30.5-trixie@sha256:b972f831f200b19ef0767938224f9711e74cd783718738cd7405d5cabf75c442# Total: 13 (HIGH: 13, CRITICAL: 0)On the Grype side, the equivalent is --only-fixed: with it,
--fail-on critical exits with 0, because none of the 18 Critical CVEs
has a fix. A wont-fix CVE will not go away on its own: the only way out is
to change the base image, or to document it.
Step 5, Verify and report
After the fix, rescan the shipped artifact and check that the CVE has gone: it is trivial and often forgotten. A monthly or per-sprint report then gives the trend. It covers the number of CVEs detected, triaged and fixed, the SLA compliance rate, the exceptions and their due dates.
These figures feed the security KPIs, which measure the posture over time. A workflow without reporting may work, but nobody can prove it.
Integrating SBOM and VEX into your pipeline
A mature pipeline produces the SBOM and the VEX as artifacts in their own right, alongside the image. Each build thus leaves its own evidence, and the question "are we affected by this new CVE?" is settled in a few minutes, by a query on the archived SBOMs, instead of several days.
A complete GitLab CI pipeline
The official Syft and Grype images contain no shell: GitLab cannot
run a script: in them. Measured: sh cannot be found in
anchore/syft:v1.52.0. You need their -debug variants, which embed
BusyBox, with entrypoint: [""] and the absolute path of the binary.
stages: - scan
generate-sbom: stage: scan image: name: anchore/syft:v1.52.0-debug@sha256:dd31bb351e28f958f2911dc2c53ef34340f3d61d5feb624e1ac061ccd0f87018 entrypoint: [""] variables: SYFT_REGISTRY_AUTH_AUTHORITY: $CI_REGISTRY SYFT_REGISTRY_AUTH_USERNAME: $CI_REGISTRY_USER SYFT_REGISTRY_AUTH_PASSWORD: $CI_REGISTRY_PASSWORD script: - /syft "registry:$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA" -o cyclonedx-json=sbom.json artifacts: paths: [sbom.json]
scan-vulnerabilities: stage: scan image: name: anchore/grype:v0.119.0-debug@sha256:166001ed93ae8463acad508c746586ced665abe9d817ae452ed9b781ddb6cebb entrypoint: [""] needs: [generate-sbom] script: # Fails (code 2) on any critical CVE that has a published fix - /grype sbom:sbom.json --only-fixed --fail-on criticalBoth scripts were replayed in these images, pinned by digest, on an image pushed
to a local registry: Syft writes the SBOM, Grype exits with 0. The
plumber analysis, which requires the API of a GitLab instance, could not be
run. The job comment states exactly what the rule blocks: without
--only-fixed, critical CVEs with no fix would block the pipeline with no
action possible.
Applying a VEX to the scan
The VEX lives in the repository, next to the code, or is attached to the
image as an attestation with
cosign attest. Here is a
minimal OpenVEX document: its justification is an example, to be
established by your own analysis of the component.
{ "@context": "https://openvex.dev/ns/v0.2.0", "@id": "https://exemple.fr/vex/nginx-2026-10-01", "author": "Equipe plateforme", "timestamp": "2026-10-01T10:00:00Z", "version": 1, "statements": [ { "vulnerability": { "name": "CVE-2011-3389" }, "products": [ { "@id": "pkg:oci/nginx@sha256%3Ab972f831f200b19ef0767938224f9711e74cd783718738cd7405d5cabf75c442", "subcomponents": [{ "@id": "pkg:deb/debian/libgnutls30t64" }] } ], "status": "not_affected", "justification": "vulnerable_code_not_in_execute_path" } ]}trivy image --vex vex/nginx.openvex.json \ nginx:1.30.5-trixie@sha256:b972f831f200b19ef0767938224f9711e74cd783718738cd7405d5cabf75c442# 389 vulnerabilities instead of 390: CVE-2011-3389 no longer appearsThe product is designated by the image purl, digest included: a VEX written for another version of the image does not apply. This is intended, since the justification only holds for the analysed artifact.
Troubleshooting
A creaking CVE workflow almost always shows one of the following symptoms. The table links each signal to its likely cause and to the corrective action, so you treat the cause rather than the symptom.
| Symptom | Likely cause | Solution |
|---|---|---|
| Too many alerts, demotivated team | Triage on severity alone | Triage by exposure, KEV and EPSS; write VEX documents for recurring cases |
| The pipeline blocks with no possible action | wont-fix critical CVEs | --ignore-unfixed (Trivy) or --only-fixed (Grype), and change the base image |
| Two scanners disagree on severity | Different scoring sources (distribution, NVD) | Set the reference source in the policy, compare on critical images |
| A GitLab job fails without running the script | Image without a shell or with an imposed ENTRYPOINT | -debug variant and entrypoint: [""] |
| CVE with no CVSS score or details | CVE "Not Scheduled" at the NVD since April 2026 | Cross-check with Vulnrichment and OSV.dev |
| The fixed CVE comes back | Base image not updated | Automate version bumps (Renovate, Dependabot) |
Key points
- A CVE identifies a flaw; CVSS gives its severity, EPSS its probability of exploitation within 30 days, KEV confirmed exploitation.
- You triage by exposure, then KEV, EPSS and CVSS: on nginx, the flaw with the highest EPSS (73.3%) is rated Negligible.
- Severity depends on the source: the same CVE is MEDIUM for Trivy and Critical for Grype.
- Deadlines per priority (24 h, 7 d, 30 d, 90 d) avoid endless trade-offs; an exception carries a review date.
--ignore-unfixedand--only-fixedseparate what can be fixed from what requires an image change.- Since 15 April 2026, the NVD no longer enriches everything: cross-check with Vulnrichment and OSV.dev.
- The SBOM answers "are we affected?", the VEX documents what is not exploitable, digest included.
- Scanners are dependencies: minimum version, digest, security advisories followed.
Knowledge check
Check that you have grasped the essentials of this lesson. The questions only cover what has just been explained here.
Knowledge check
Check what you have learned with this interactive quiz
How it works
- The timer starts when you click Start
- Multiple choice, true/false and short answer questions
- You can move between questions
- Detailed results are shown at the end
Starts the quiz and the timer
Review
(0/0)Score by domain
A domain groups several guides and gets several questions, so its score is more reliable than a per-topic score, which often rests on a single question.
Summary
Strengths
No topic passed at 80% on at least two questions yet.
Weak areas
No topic below the pass mark.
Recommended next
Hints before you try again?
Continue the course: Security debt: understanding a cost that piles up
A new full quiz with random questions
Work again only on the questions you missed
FAQ: frequently asked questions
The questions below cover the most common doubts about vulnerability management: the difference between CVSS and EPSS, prioritisation, the role of the SBOM and the VEX, and CVEs left without a score at the NVD.
CVE-2021-44228 for the well-known Log4Shell flaw. It acts as a case number: it identifies the flaw, but says nothing on its own about its urgency. It is CVSS and EPSS that then assess the severity and the real probability of exploitation.nginx:1.30.5-trixie image, the flaw with the highest EPSS (CVE-2011-3389, 73.3%) is rated Negligible, while Grype's Critical CVEs all have an EPSS below 1%. The details are in the CVE, CVSS and EPSS lesson.not_affected, together with a justification. A scanner that reads it removes the CVE from its report: measured on the nginx image, trivy image --vex goes from 390 to 389 vulnerabilities. The decision is written once, versioned in Git or attached to the image with cosign attest.