Key takeaways
- Every vulnerability entry in a CycloneDX document can carry an analysis object with up to three fields.
- Both are valid. Embedded VEX is a vulnerabilities array inside the same CycloneDX document that also lists the product's components — convenient when one team produces both together.
- Yes — the enum values are consumed programmatically, not just displayed.
A CycloneDX VEX statement answers the question a raw vulnerability scan cannot: not "does this component contain CVE-2024-XXXXX," but "does this product, as shipped, actually expose that vulnerability." CycloneDX's own capability page describes VEX as conveying "the exploitability of vulnerable components in the context of the product in which they're used," using a structured analysis object with a fixed set of state, justification and response values that scanners such as Trivy and platforms such as Dependency-Track already read to decide whether to keep flagging a given CVE for a given product.
What does a CycloneDX VEX analysis block actually contain?
Every vulnerability entry in a CycloneDX document can carry an analysis object with up to three fields. state records the current assessment: resolved, resolved_with_pedigree, exploitable, in_triage, false_positive, or not_affected. justification is required whenever state is not_affected, and must be one of nine specific reasons — code_not_present, code_not_reachable, requires_configuration, requires_dependency, requires_environment, protected_by_compiler, protected_at_runtime, protected_at_perimeter, or protected_by_mitigating_control. response is a separate array recording vendor action: can_not_fix, rollback, update, will_not_fix, or workaround_available. A typical "we checked, it doesn't apply" statement looks like {"state": "not_affected", "justification": "code_not_reachable"} — a specific, falsifiable claim, not a generic risk label.
Where did this come from, and has it changed?
VEX support shipped in CycloneDX v1.4, released January 2022 — the version CycloneDX's own history page credits as "the first specification to introduce vulnerability sharing and transparency, including Vulnerability Disclosure Reports (VDR) and Vulnerability Exploitability eXchange (VEX)." That release also introduced the bom-link reference type, which is what lets a VEX document point at components defined in a separate SBOM file rather than repeating them. Versions 1.5 and 1.6 broadened the surrounding vulnerabilities data model and added unrelated capabilities (CBOM for cryptographic assets, CDXA for attestations), but the analysis object's enum values covered above have been stable since 1.4 — a VEX statement written for a 1.4 SBOM reads the same way against a 1.6 one.
Does VEX have to be embedded in the SBOM, or can it be separate?
Both are valid. Embedded VEX is a vulnerabilities array inside the same CycloneDX document that also lists the product's components — convenient when one team produces both together. Standalone VEX is a second CycloneDX document containing only the vulnerabilities array, with each entry's affects field pointing at a component defined in a separately published BOM via a bom-link URN. Standalone VEX is the better fit when a PSIRT publishes updated exploitability assessments on its own cadence, independent of when a new SBOM is generated from a build. CISA's own guidance on VEX implementations lists CycloneDX as one of four formats in current use — alongside the CSAF 2.0 VEX profile, OpenVEX and SPDX 3.0 — without mandating any one of them; see our CSAF 2.0 and VEX guide and CycloneDX vs SPDX guide for how the four compare.
Do tools actually act on these fields, or is it just documentation?
Yes — the enum values are consumed programmatically, not just displayed. Trivy's own documentation describes VEX as a way to "filter detected vulnerabilities," supporting VEX supplied as a local file, an OCI registry attestation, a remote VEX repository, or a reference embedded in the SBOM itself; a component whose CycloneDX VEX entry carries not_affected with a valid justification is excluded from Trivy's reported findings rather than merely annotated. Dependency-Track, the most widely deployed open-source SBOM analysis platform, similarly ingests analysis.state to re-triage findings automatically rather than requiring a human to re-review every CVE on every rebuild. That is the practical payoff of a structured enum over free-text notes: a scanner can act on not_affected + code_not_reachable without a human reading a sentence first.
SoftWSP generates firmware SBOMs directly from binary images and can produce the matching CycloneDX VEX analysis — state, justification and response — for vulnerabilities its own scans flag, so a device maker's downstream customers and scanners stop re-flagging CVEs that do not actually apply to the shipped firmware. See PSIRT as a service for how that fits into an ongoing vulnerability-response process.
This guide is based on CycloneDX's own capability and history pages, the CycloneDX Python library's published enum definitions (which mirror the JSON/XML schema), CISA's Minimum Requirements for VEX document, and Trivy's published VEX documentation, all read directly for this guide. Enum values were cross-checked against the CycloneDX Python library's source rather than taken from a single secondary summary; verify against the current CycloneDX JSON schema at github.com/CycloneDX/specification before relying on a specific field name in tooling you build.
Sources
- CycloneDX: Vulnerability Exploitability (VEX) capability page
- CycloneDX: Specification version history
- CycloneDX Python Library documentation: cyclonedx.model.impact_analysis (ImpactAnalysisState, ImpactAnalysisJustification, ImpactAnalysisResponse enums)
- CISA: Minimum Requirements for Vulnerability Exploitability eXchange (VEX)
- Trivy documentation: VEX (Vulnerability Exploitability eXchange) support
Frequently asked questions
What is CycloneDX VEX?
VEX (Vulnerability Exploitability eXchange) is a built-in capability of the CycloneDX Bill of Materials standard that, per CycloneDX's own capability page, 'conveys the exploitability of vulnerable components in the context of the product in which they're used.' Instead of a scanner simply listing every CVE that matches a component's version string, a CycloneDX VEX statement lets a vendor assert, per component and per vulnerability, whether that vulnerability is actually reachable and exploitable in the product as shipped.
When did CycloneDX add VEX, and did the schema change since?
VEX arrived in CycloneDX v1.4, released in January 2022 — CycloneDX's own history page describes it as 'the first specification to introduce vulnerability sharing and transparency, including Vulnerability Disclosure Reports (VDR) and Vulnerability Exploitability eXchange (VEX).' Later versions (1.5, 1.6) expanded the vulnerabilities/analysis data model and the wider BOM capability set (CBOM for cryptographic assets, CDXA for attestations), but the core VEX analysis object's enum values described in this guide have not changed since 1.4.
What values can the analysis.state field actually take?
Per the CycloneDX schema (confirmed against the CycloneDX Python library's own enum definitions, which mirror the JSON/XML schema 1:1): resolved, resolved_with_pedigree, exploitable, in_triage, false_positive, and not_affected. A VEX consumer such as a scanner is expected to treat not_affected, false_positive and resolved as reasons to stop flagging a given CVE for that specific product, and in_triage / exploitable as reasons to keep flagging it.
What is the justification field for, and what are its allowed values?
When analysis.state is not_affected, CycloneDX requires a justification explaining why. The allowed values are: code_not_present, code_not_reachable, requires_configuration, requires_dependency, requires_environment, protected_by_compiler, protected_at_runtime, protected_at_perimeter, and protected_by_mitigating_control. A separate response array (can_not_fix, rollback, update, will_not_fix, workaround_available) records what the vendor is doing about vulnerabilities that are not dismissed as not_affected.
Does a VEX statement have to live inside the same file as the SBOM?
No. CycloneDX supports both embedded VEX (a vulnerabilities array inside the same BOM document that also lists components) and standalone VEX (a separate CycloneDX document containing only vulnerabilities and analysis, referencing an external BOM's components via the bom-link mechanism introduced alongside 1.4's VEX support). CISA's own VEX guidance lists CycloneDX as one of four VEX implementations in active use, alongside the CSAF 2.0 VEX profile, OpenVEX and SPDX 3.0 — see our [CSAF 2.0 and VEX guide](/guides/csaf-vex-for-device-makers/) for how the CSAF profile compares.
In this cluster


