A VEX statement says whether one product is affected by one vulnerability, and why; it does not remove the component from the SBOM. Issue not_affected only for an exact product version or digest, with a justification your build evidence supports, such as vulnerable_code_not_present. As a consumer, accept statements only from issuers you trust and for the exact product you run, and keep the original finding so that a build or configuration change can reopen it.
- VEX states impact, not presence: a
not_affectedcomponent still belongs in the SBOM. - OpenVEX v0.2.0 was still a draft on 11 October 2026; CSAF 2.0 and CycloneDX 1.7 express the same four states under different names.
- Trivy and Grype match OpenVEX products by package URL, and a product purl without a version or digest suppresses the finding in every version of the image.
vulnerable_code_not_presentandvulnerable_code_not_in_execute_pathare different claims; a default-off feature a customer can enable supports neither.- A valid signature proves who issued a statement, not that it is still true for the build you deploy.
- Scan with
trivy image --vex <file> --show-suppressedand confirm that only the named package and vulnerability move to the suppressed list. - Scan a different image digest with the same VEX file and confirm that nothing is suppressed.
- Reject a statement whose product purl has no version or digest unless the issuer means it to cover every version.
- Confirm that every
not_affectedstatement carries a justification label or an impact statement, as OpenVEX requires. - Rebuild with the vulnerable code enabled and confirm that your process reopens the finding.
- Applies to
- OpenVEX v0.2.0 (draft specification)
- Trivy and Grype Trivy 0.75.0 and Grype 0.120.1 (--vex with OpenVEX)
- vexctl 0.4.4
Producer/consumer VEX decisions. Status claims must match the precise artifact and remain revisitable.
- Part of
- Learning path: From source to trusted release (step 12 of 12). Workflows: Fix a finding, Respond to a vulnerability
Learning goals and prerequisites
What you will learn
- Distinguish component presence from product impact.
- Select a justification supported by build evidence.
- Reject stale or contradictory VEX despite a valid signature.
Before you start
Product security teams who publish VEX statements and engineers who apply supplier VEX to scanner results
- Distinguish component presence, vulnerability status, affected product and supporting justification.
Refresh first: Triage new dependency vulnerability findings
Go to the guided practiceFrom a release SBOM to a filtered scanner finding
- GenerateThe producer's build generates the SBOM from the built artifact, listing each component by purl, and records the artifact digest.
- AttachThe SBOM ships with the release as a signed attestation bound to that digest, so a consumer can tell which artifact it describes.
- Assess and publish VEXWhen an advisory matches a component, the producer's security team assesses that exact product and publishes a signed VEX statement: product purl with digest, vulnerability, status, and a justification or impact statement.
- Verify and matchThe consumer verifies who signed the SBOM and the VEX, then its scanner matches advisories to the components by purl and reports every finding.
- Filter by policyLocal policy applies a statement only from a trusted issuer whose product matches the deployed digest.
not_affectedandfixedfindings move to a suppressed list that names the statement;affectedandunder_investigationfindings stay open. - ReopenA new digest, a build that includes the vulnerable code, or local evidence that contradicts the justification sends the finding back to review. The original detection is never deleted.
An SBOM states component presence under its declared scope. A vulnerability match connects a component to an advisory. VEX supplies a product-specific assessment of that vulnerability's impact. These are different records. A not_affected statement does not mean the component was absent from the inventory or that the advisory is false. It means the issuer asserts a particular product is not affected, with a reason whose validity depends on scope and evidence.
OpenVEX provides the statuses affected, not_affected, fixed and under_investigation, and requires a not_affected statement to carry a justification label or an impact statement. Its repository describes the specification as draft; the example here pins the v0.2.0 context and should not be described as a final universal standard. A consumer must know the issuer it trusts and what product identifiers mean. A claim about one product version, platform or feature configuration does not automatically apply to another merely because they share a component name.
Suppliers publish VEX in three formats. CSAF 2.0, an OASIS Standard also published as ISO/IEC 20153:2025, has a VEX profile used in vendor security advisories; CSAF 2.1 is still a committee draft. CycloneDX carries VEX in its vulnerabilities array, either inside an SBOM or as a separate document. The four states line up, but the justification vocabularies differ: OpenVEX and CSAF share five labels, while CycloneDX has nine of its own. Only two map exactly, vulnerable_code_not_present to code_not_present and vulnerable_code_not_in_execute_path to code_not_reachable, so keep the original label when you convert.
| Meaning | OpenVEX v0.2.0 | CSAF 2.0 VEX profile | CycloneDX 1.7 analysis |
|---|---|---|---|
| The product is affected | affected, with an action_statement | known_affected, with a remediation | exploitable, with a response |
| The product is not affected | not_affected, with a justification or impact_statement | known_not_affected, with a flag label or impact statement | not_affected, with a justification |
| The product is fixed | fixed | fixed | resolved, or resolved_with_pedigree |
| Not yet known | under_investigation | under_investigation | in_triage |
| The advisory does not apply to the component at all | No status | No status | false_positive |
The fictional Larch 5.0 includes codec 2.4, but its compiled build excludes the vulnerable legacy decoder. Evidence E17 identifies the exact product digest and symbol/call analysis supporting that exclusion. A defensible not_affected statement binds the product version, vulnerability and justification, with an impact explanation that a reviewer can challenge. Record the evidence reference and limitations outside or alongside the exchange document as appropriate to the chosen format.
The distinction between vulnerable code absent and vulnerable code unreachable matters. The former claims the relevant code is not in the artifact; the latter claims it cannot execute through the product's paths. A disabled default option is weaker if customers can turn it on. The illustrative document below uses vulnerable_code_not_present because the fixture explicitly supplies compiled exclusion evidence. If that evidence were missing, under_investigation would be the honest state. Never select a convenient justification merely to silence a scanner.
{
"@context": "https://openvex.dev/ns/v0.2.0",
"@id": "https://security.example/vex/larch-5-adv8",
"author": "https://security.example/larch-team",
"timestamp": "2026-10-07T12:00:00Z",
"version": 1,
"statements": [
{
"vulnerability": {
"name": "ADV-8-FICTIONAL"
},
"products": [
{
"@id": "pkg:generic/larch@5.0.0"
}
],
"status": "not_affected",
"justification": "vulnerable_code_not_present",
"impact_statement": "For artifact L50 only, build evidence E17 shows legacy decoder excluded; no claim for plugins or L51."
}
]
}A consumer should authenticate the issuer, match the product identity and vulnerability, inspect the stated rationale and reconcile contradictory local evidence. A signed supplier statement can still be out of scope or stale. Maintain a local acceptance policy that defines when VEX suppresses an alert, when it only changes priority and when further review is required; /domains/supply-chain/dependencies/evidence-based-triage/ routes each finding to one such outcome. The underlying detection should remain recoverable, including the original component identity and advisory data.
In the fixture, the issuer is trusted for Larch, but the deployed product is L51, whose build now includes the decoder. The previous L50 statement cannot suppress L51's finding. A new statement claiming all Larch 5.x products are not affected conflicts with E18 and must be investigated even if its signature verifies. Timestamps and version numbers help sequence statements; the most recently received document is not necessarily the newest authoritative revision. Consumers need a policy for supersession and rollback of incorrect assertions.
Scanners match OpenVEX statements by package URL, so the purl rules in /domains/supply-chain/sbom/formats-and-identifiers/ decide what a statement covers. Name the product as the image, bound to its digest, and the package the statement is about as a subcomponent. The script below writes such a statement with vexctl and applies it with Trivy and Grype. Tested on the alpine:3.22.4 image, CVE-2026-14456 was reported for both libcrypto3 and libssl3; with the statement, both scanners suppressed only the libcrypto3 finding and still reported libssl3. A statement that named the alpine:3.22.6 digest instead suppressed nothing in the same scan.
The near miss is the product identifier. When the product purl had no digest, pkg:oci/alpine?repository_url=..., both scanners suppressed the finding as well, because a purl without a version matches every version of the image. That is the broad suppression the next section warns against. --show-suppressed keeps suppressed findings visible with the statement that suppressed them. Trivy applies CycloneDX VEX only when scanning an SBOM, matching components by BOM-Link rather than by purl.
# Product: the released image by digest. Subcomponent: the package the statement is about.
PRODUCT="pkg:oci/cedar@${DIGEST}?repository_url=ghcr.io%2Facme%2Fcedar"
vexctl create --author "Acme Product Security" \
--product "$PRODUCT" --subcomponents "pkg:apk/alpine/libcrypto3@3.5.6-r0" \
--vuln CVE-2026-14456 --status not_affected \
--justification vulnerable_code_not_in_execute_path \
--impact-statement "Call-graph evidence E31: Cedar never reaches the affected code." \
--file cedar-2.3.openvex.json
# Consumers apply it; every other package and vulnerability is still reported
trivy image --vex cedar-2.3.openvex.json --show-suppressed "ghcr.io/acme/cedar@$DIGEST"
grype "ghcr.io/acme/cedar@$DIGEST" --vex cedar-2.3.openvex.json --show-suppressedA VEX decision should name reopening triggers: product digest changes, affected code or feature becomes available, advisory scope changes, issuer trust is withdrawn, or local evidence contradicts the rationale. For not_affected due to code absence, a build change enabling that code is decisive. For a fixed statement, verify the actual patched product rather than accepting a future release announcement. Uncertainty should produce review, not silent permanent suppression.
There is an operational tradeoff between reducing noise and preserving accountability. Broad wildcard suppression is easy to operate but can hide real exposure after a configuration change, and a product purl without a version is such a wildcard. Very narrow artifact-specific statements cost more to maintain but can be joined to precise release evidence. Use the granularity required by the claim and automate re-evaluation on relevant changes. The exercise is fully self-contained: the JSON is illustrative VEX data, the evidence is fictional, and no scanner or production suppression is changed.
Guided practice
A consumer has a VEX statement for Larch 5.0 and must process a new release and contradictory update.
Supplied scenario material
ADV-8-FICTIONAL: affects codec 2.4 legacy decoder when it executes.
L50 = Larch 5.0.0, codec 2.4; E17 compiled legacy decoder excluded; product artifact L50.
V1 = printed statement, version 1, trusted Larch issuer, covers pkg:generic/larch@5.0.0 only.
L51 = Larch 5.1.0, codec 2.4; E18 legacy decoder included; customer can enable legacy mode.
V2 = trusted signature, version 2, claims not_affected for all Larch 5.x because legacy mode is disabled by default; no additional evidence.
Current deployment: L51, legacy mode enabled.
Consumer policy: accept only authenticated, exact-scope statements with rationale consistent with local evidence; contradictions enter review; retain original alert.1. Interpret V1
Explain why presence of codec 2.4 does not contradict V1 for L50.
Expected result and reasoning
The component can be present while its vulnerable code is excluded from this product artifact. E17 supports that specific justification for L50; the SBOM entry should remain.
2. Match deployment scope
Decide whether V1 can suppress the L51 alert.
Expected result and reasoning
No. L51 is a different product version and E18 changes the relevant build fact. Exact-scope policy rejects reuse of V1 for L51.
3. Evaluate the signed update
Assess V2 against the current deployment and name the contradiction.
Expected result and reasoning
Do not suppress. Disabled by default does not establish unreachability when the customer enabled legacy mode. E18 plus current configuration contradict the broad rationale despite trusted authentication.
4. Choose the local state
Write a local response without rewriting the supplier evidence.
Expected result and reasoning
Retain the component/advisory alert, V1 and V2 as received, and record V2 disputed for L51. Treat current affected-path evidence as requiring remediation review; obtain corrected supplier scope and plan disabling or patching the decoder.
5. Specify the regression
Give an exact future re-evaluation trigger and expected outcome.
Expected result and reasoning
Any build enabling the decoder invalidates an E17-style absence claim. For L51 with legacy enabled, policy must keep the alert active; for exact L50 with authenticated V1/E17, it may apply the bounded
not_affecteddecision.
The JSON is a teaching document with fictional vulnerability/product identifiers. It is not a real supplier assertion, schema-validation result or suppression instruction.
Save a draft on this device to resume later. Automatic saving begins after you choose Save. Drafts are never sent by this application; use synthetic data.
No draft saved on this device yet.
Sources & further reading
The standards, specifications and documentation behind the guidance above. Each is the evidence for a claim on this page; consult it for detail beyond the defaults and versions listed here.
- OpenVEX specification. v0.2.0 (draft).
- CycloneDX specification overview, version 1.7. Version 1.7 (October 2025).
- OSV: vulnerability data and package queries
- OASIS: Common Security Advisory Framework Version 2.0. OASIS Standard, 18 November 2022.
- CycloneDX: VEX capabilities
- Trivy: applying VEX from a local file
- Grype: vulnerability scanner (VEX support)
- vexctl
- CISA: Minimum Requirements for Vulnerability Exploitability eXchange (VEX). 21 April 2023.
Reference information
- SDLC phase
- ReleaseMaintenance
- Role
- AppSec engineers