How do you feed VEX statements to a Trivy 0.74 scan so not_affected findings drop out, and how do you audit what they suppressed?
answer
- one repeatable flag, four kinds of source
- PURL is the product identifier
- format support differs by target
- --show-suppressed
- a versionless PURL matches every version
basics
~20 sPass --vex with a local OpenVEX, CSAF or CycloneDX file, or with repo, oci or sbom-ref; a statement whose PURL matches a found package and says not_affected or fixed removes that finding. Adding --show-suppressed lists what VEX removed and why.
solid answer
~40 s`--vex`, still experimental in 0.74, is repeatable and ordered by priority: a file path, `repo` (VEX Hub or a configured VEX repository), `oci` (VEX attestations discovered beside an image) or `sbom-ref` (VEX URLs in a CycloneDX SBOM's `externalReferences`). OpenVEX and CSAF work on every target; CycloneDX VEX applies only when scanning a CycloneDX SBOM, as a separate document that references it. Trivy matches statements by PURL, so take PURLs from Trivy's own JSON report; a matching `not_affected` or `fixed` statement removes the finding, after severity, status and ignore-file filtering. To audit, add `--show-suppressed`: the table lists each suppressed CVE with its status, statement and source, and JSON exports them as `ExperimentalModifiedFindings`. Watch scope — a PURL without a version matches every version.
code
json · 17 lines{
"@context": "https://openvex.dev/ns/v0.2.0",
"@id": "https://vex.example.internal/payments-api/2026-09",
"author": "Platform Security, example.internal",
"timestamp": "2026-09-14T09:00:00Z",
"version": 1,
"statements": [
{
"vulnerability": {"name": "CVE-2019-8457"},
"products": [
{"@id": "pkg:deb/debian/[email protected]+dfsg1-0.8"}
],
"status": "not_affected",
"justification": "vulnerable_code_not_in_execute_path"
}
]
}go deeper
Recall that --vex feeds a VEX document to Trivy and that a matching not_affected statement removes the finding from the report.
Explain the four source kinds, which formats work on which targets, PURL matching, and that VEX runs after severity, status and ignore-file filtering.
Show you audit suppressions with --show-suppressed and the JSON records, avoid versionless products, and know when CycloneDX VEX silently does nothing.
Decide whose VEX statements may delete findings from your reports, how they are reviewed and expired, and how suppressions stay visible to auditors.
## What --vex does in the pipeline **VEX** (Vulnerability Exploitability eXchange) is a document in which a supplier or your own team states, per vulnerability and product, whether that product is affected. What each status and justification claims, and who may assert it, is a supply-chain subject of its own; this answer is about how Trivy **consumes** such statements. Trivy applies VEX as the **last** filtering step, after severity, status, the ignore file and any Rego policy. A finding that matches a statement with status `not_affected` or `fixed` is removed from the result; statements saying `affected` or `under_investigation` remove nothing. Each removal is recorded, and a log line such as `Filtered out the detected vulnerability` names the format, the CVE, the status and the justification. The feature is marked **experimental** in 0.74, so its behaviour may change between releases. ## The four kinds of source `--vex` can be repeated, and the order you give is the priority order. | Value | Where statements come from | |---|---| | a file path | a local OpenVEX, CSAF or CycloneDX document | | `repo` | VEX repositories configured in `repository.yaml`, by default Aqua's VEX Hub on GitHub | | `oci` | VEX attestations attached to the scanned image in its registry | | `sbom-ref` | VEX URLs listed as `exploitability-statement` external references in a CycloneDX SBOM | The flag's own help text in 0.74 names only `repo`, `oci` and a file path; the documentation and the source also accept `sbom-ref`. ## Format support by target - **OpenVEX** and **CSAF** apply to every target: image, filesystem, repository, VM image, Kubernetes and SBOM. - **CycloneDX VEX** applies only when the target is a CycloneDX SBOM, and only in the independent form — a separate VEX document whose `affects` entries point into that SBOM with BOM-Links. A CycloneDX VEX file passed to `trivy image` filters nothing. ## How a statement matches 1. The vulnerability ID in the statement must match the finding's ID. 2. The product must be a **PURL** that matches the package Trivy found; the reliable way to get it right is to copy the `PURL` from Trivy's JSON report. 3. Matching follows a community convention: a PURL **without a version** matches every version, a PURL without qualifiers matches every qualifier variant, and a PURL with qualifiers matches only packages carrying the same values. 4. OpenVEX **subcomponents** narrow a statement about an image to the specific package inside it, which reduces the chance of over-filtering. Trivy applies statements over its dependency graph: a statement that an intermediate module is not affected by a CVE in a subcomponent suppresses the finding only when every path from the root to the vulnerable package runs through a not-affected parent; one uncovered path keeps it. ## Auditing what was suppressed - `--show-suppressed` adds a *Suppressed Vulnerabilities* table listing package, CVE, severity, status, statement and source, such as `CSAF VEX` or `.trivyignore.yaml`. - In JSON the same records appear as `ExperimentalModifiedFindings`. - Findings dropped by `--severity` or `--ignore-status` never appear there; only ignore-file and VEX suppressions are recorded. ## Pitfalls - **Versionless products.** A `not_affected` statement on `pkg:deb/debian/libdb5.3` silences the CVE for every future version of that package, including one where the vulnerable path becomes reachable. - **Wrong format for the target.** CycloneDX VEX with an image target does nothing and raises no alarm. - **Network sources offline.** `repo` fetches from GitHub; in a restricted network, self-host the repository or use `--skip-vex-repo-update`. - **Partial package lists.** `--pkg-relationships` cannot be combined with `--vex`, because a filtered package list could make VEX misapply. - **Trust.** `--vex repo` and `--vex oci` accept other parties' statements; decide whose statements you are willing to let delete findings from your report. ## A minimal workflow 1. Scan once with `--format json` and copy the exact `PURL` of the package the assessment covers. 2. Write the statement — OpenVEX for an image, filesystem or repository target — with a **versioned** PURL and the justification your assessment supports. 3. Keep the document in version control beside the service, so a reviewer sees who asserted what and when. 4. Scan with `--vex <file> --show-suppressed` and confirm the finding moved to the suppressed table with the expected source. 5. When the package version changes, the versioned PURL stops matching and the finding returns, which forces a fresh assessment instead of an inherited one.
- Why does a CycloneDX VEX file have no effect on trivy image?Trivy supports CycloneDX VEX only in the independent BOM-plus-VEX form and only when the scan target is a CycloneDX SBOM, because the statements reference components by BOM-Link. For an image, write the statements as OpenVEX or CSAF, or scan the image's CycloneDX SBOM with `trivy sbom` and pass the VEX there.
- What happens to --vex repo on a runner with no route to GitHub?The default VEX repository is VEX Hub, fetched over HTTPS from GitHub, so the update fails. Self-host a copy, point `repository.yaml` (created by `trivy vex repo init`) at the internal server with the default repository disabled, or pass `--skip-vex-repo-update` to use what is already cached.
- Can --vex be combined with --pkg-relationships root,direct?No. Trivy's documentation states that `--pkg-relationships` cannot be used with `--vex`, because filtering packages by relationship may leave an incomplete package list, and VEX is applied across the dependency graph.
saying these in an interview costs you the question
- --vex marks findings not_affected but leaves them in the report
- Any VEX format works with any Trivy target
- A statement saying under_investigation also hides the finding
- Once VEX filters a finding, there is no way to see it again
- Trivy matches VEX statements by CVE ID alone, whatever the package