A Vulnerability Disclosure Report (VDR) is a supplier's statement of every known vulnerability that affects a product, with the analysis and plan for each one. A Bill of Vulnerabilities (BOV) is a CycloneDX document that carries vulnerability intelligence on its own, between tools or organisations. Both use the same CycloneDX vulnerabilities array; they differ in scope and promise.
That one sentence of difference matters more than it looks. A VDR is a claim about completeness for a specific product: "these are all the vulnerabilities we know of that touch this release, and here is what each one means for you." A BOV makes no claim about any product of yours. It is a container for vulnerability facts, such as a feed from a research team, an advisory database export or a list handed from a scanner to a ticketing system. VEX, the third document people confuse with both, answers a narrower question again: is this product exploitable through this vulnerability, yes or no, and why.
Below: the CycloneDX vulnerability object field by field, schema valid JSON for all three documents, a release walkthrough with open source tools, and when a buyer should ask for which. It also covers something many VDR explainers miss: NIST revised the guidance that made "VDR" a procurement term in November 2024.
Key takeaways
- A VDR asserts all known vulnerabilities affecting a product, component or service, including previously unknown ones the supplier found itself, with an impact analysis and a plan for each. CycloneDX's VDR page says VDRs are also known as Vulnerability Advisory Reports (VAR) and aligns them with ISO/IEC 29147.
- A BOV is CycloneDX's name for exchanging vulnerability intelligence between systems, teams and organisations. It is not tied to one product's release and makes no completeness promise about your software.
- VEX states exploitability in context. CISA's minimum requirements define four statuses: not affected, affected, fixed and under investigation.
- NIST SP 800-161 Rev. 1 (May 2022) recommended that suppliers give customers a VDR. The 1 November 2024 update rewrote that control guidance around vulnerability reports and vulnerability advisory reports under ISO/IEC 29147, and the term "VDR" no longer appears in the updated text.
- In CycloneDX, all three use the same
vulnerabilitiesarray, added in version 1.4 (January 2022). The key fields areratings,affectsandanalysis. Version 1.5 addedworkaroundand completeness throughcompositions. - Ask for a VDR to learn what is wrong with a product and what the supplier will do. Ask for VEX to silence false positives in your own scanners. Many buyers need both.
What is a VDR?
A Vulnerability Disclosure Report is a document a software supplier publishes to tell customers which vulnerabilities affect a product, what each one means for that product, and what the supplier plans to do. The CycloneDX project describes its VDR capability as communicating "known and unknown vulnerabilities" for first party and third party components, and representing how complete the supplier's vulnerability knowledge is.
Three properties make a document a VDR rather than a list of scanner findings:
- It is about a product. A VDR has a subject: a named product and version, usually the same one an SBOM describes. Every vulnerability in it is linked to a component or service of that product.
- It claims completeness. The CycloneDX project's own comparison in its examples repository says a VDR "asserts all vulnerabilities affecting a product, component or service." If a known vulnerability affects the product and is missing, the VDR is wrong, not just incomplete.
- It carries analysis and intent. Each entry says whether the vulnerability actually affects the product, with reasoning, and what the supplier will do: update, roll back, publish a workaround, or decline to fix.
The second property separates a VDR from a scan report. A scan report says what one tool matched on one day. A VDR is the supplier's considered position, including vulnerabilities no public database knows yet, such as a researcher's bug bounty report. CycloneDX names coordinated disclosure and bug bounty programs as VDR use cases.
If you already know the difference between an SBOM, a scan report and VEX, the live post SBOM vs vulnerability scan report vs VEX: which answers what covers that ground. This post goes into the VDR and BOV documents themselves.
How does NIST SP 800-161r1 refer to a VDR?
NIST Special Publication 800-161 Revision 1, "Cybersecurity Supply Chain Risk Management Practices for Systems and Organizations," was published in May 2022. In its original text, the supplemental supply chain guidance for control RA-5 (Vulnerability Monitoring and Scanning) said enterprises "may consider providing customers with a Vulnerability Disclosure Report (VDR)" to show that vulnerability assessments for components listed in SBOMs were proper and complete. The same paragraph said the VDR should:
- include analysis and findings describing the impact, or lack of impact, of each reported vulnerability on a component or product;
- contain information on plans to address the CVE;
- be published in a secure portal available to customers;
- be signed with a trusted, verifiable private key, with a timestamp for the signature.
It also suggested a separate notification channel for vulnerabilities arising between VDRs, and flowing the requirement down to contractors. A risk assessment table told acquirers to review VDRs published by software vendors. Most VDR explainers quote this wording, including the CycloneDX VDR examples page on GitHub.
What changed in the November 2024 update?
NIST issued an update to SP 800-161r1, labelled 800-161r1-upd1, with changes dated 1 November 2024. Its revision history (Appendix K) records a substantive change: "Revised discussion of vulnerability reports and vulnerability advisories in RA-5." The updated RA-5 guidance no longer uses the term VDR. Instead it describes two documents from ISO/IEC 29147:
- a vulnerability report, which a finder such as a security researcher sends to the supplier, with elements like product name, affected version, root cause, steps to reproduce, impact and severity;
- a vulnerability advisory report (VAR), which the supplier publishes after verifying the issue and developing a fix, with elements including an identifier, date and time, title, overview, affected products, intended audience, description, impact, severity, remediation, references, discovery credit, contact information, revision history and terms of use.
The update calls machine readability of the VAR good practice, not a requirement. The acquirer side changed to match: the risk assessment table now says to review vulnerability advisory reports published by software vendors.
What this means in practice:
- If a contract cites SP 800-161r1 when asking for a "VDR," the current text speaks of vulnerability advisory reports. Ask which revision the requirement was written against.
- The intent did not reverse. Both versions want suppliers to disclose vulnerabilities with impact and remediation. The update adopted an international standard's vocabulary and dropped the 2022 signing and portal specifics from that paragraph.
- CycloneDX treats VDR and VAR as two names for one capability, so a CycloneDX VDR can serve as a machine readable VAR.
The live post NIST SP 800-161: supply chain risk, what small teams take covers the rest of the publication.
How is a VDR different from VEX?
A VDR says "here is every vulnerability that affects this product and what it means." VEX says "here is whether this product is exploitable through this specific vulnerability." The first is an inventory of risk with analysis; the second is a set of status statements.
CISA's "Minimum Requirements for Vulnerability Exploitability eXchange (VEX)," published in April 2023, defines a VEX statement as conveying one status for one vulnerability and one or more products. The status must be one of:
- not_affected, which should carry a justification, or else an impact statement;
- affected, which must carry an action statement telling the user what to do;
- fixed;
- under_investigation, which is expected to change once the investigation ends.
The same document says VEX does not imply any default status that is not stated. Silence in a VEX document means nothing. That is the structural difference from a VDR, where silence about a known vulnerability is an error.
The CycloneDX project publishes its own comparison in the VDR folder of its examples repository. Summarised, with the caveat that this is one standard body's framing:
| Question | VDR | VEX |
|---|---|---|
| What does it assert? | All vulnerabilities affecting a product, component or service | Exploitability status, often used to state what a product is not affected by |
| Which vulnerabilities? | Known and previously unknown | Known (identified) vulnerabilities |
| Scope | A product or its components and services | A product, product family or a whole organisation |
| Severity and risk | Can carry modified severity in product context (environmental CVSS, OWASP risk rating) | Status and justification; no modified severity required |
| What it attests | That the supplier checked the product's dependencies and communicated the results | What does not affect the product, and optionally what does |
| Lifecycle | Published alongside the SBOM; updated when new vulnerabilities affecting the product appear or decisions change | Published alongside the SBOM; updated when new vulnerabilities are published or decisions change |
| Formats | CycloneDX | CycloneDX, CSAF, OpenVEX |
| Policy anchor | NIST SP 800-161r1 (2022 text) | CISA VEX documents |
Two caveats. The "known vulnerabilities only" line for VEX depends on format: CISA requires a vulnerability identifier, and a CycloneDX VEX can carry a supplier assigned one. And the VDR policy anchor has moved, as described above.
The live post SBOM vs VEX: when do you need each works through VEX in practice. A deeper comparison of VEX formats is in this series' post on VEX in SPDX 3 compared with CSAF, OpenVEX and CycloneDX.
Can one CycloneDX file be both a VDR and a VEX?
Yes. In CycloneDX, VDR and VEX are uses of the same object. A VDR entry with analysis.state set to not_affected and a justification is already a VEX statement; a VDR is, in effect, VEX with a completeness promise and richer risk data. The CycloneDX examples recommend keeping VEX separate from the SBOM, since inventory changes rarely and vulnerability status often, while supporting embedded VEX for point in time audit snapshots. The same applies to VDRs.
What is a CycloneDX BOV, and who exchanges one?
CycloneDX describes the Bill of Vulnerabilities as a standard way to "share vulnerability intelligence between systems, teams, and organizations." Its capability page lists what a BOV carries: severities and risk ratings, remediation details, discovery credits, affected software versions, and multiple severity and risk ratings with recommendations.
A BOV differs from a VDR in three ways:
- No product subject. A BOV usually has no
metadata.component; it describes vulnerabilities and the packages or versions they affect. - No completeness promise. It says "here are vulnerability records," not "here is everything in release 3.2.0."
- No product analysis. It normally has no
analysisblock, because exploitability needs product context. It carries source ratings instead.
Who exchanges one? The CycloneDX pages describe the pattern rather than naming parties, so these are the categories the format fits:
- Research teams and advisory publishers sending records with credits, CWEs and affected ranges to maintainers or downstream databases.
- Vulnerability databases and intelligence sources exporting records to scanners, inventories or ticketing systems.
- Internal teams, such as product security handing curated findings to engineering.
- Suppliers sharing pre release findings with a coordinator before an advisory goes public.
Because a BOV is ordinary CycloneDX, every tool that reads CycloneDX vulnerabilities can read it. That is the main argument for using it over an ad hoc JSON export.
VDR, VEX and BOV side by side
| VDR | VEX | BOV | |
|---|---|---|---|
| Question answered | Which vulnerabilities affect this product, and what are you doing? | Is this product exploitable through this vulnerability? | What vulnerability facts are you passing me? |
| Typical author | Software supplier or product security team | Supplier, maintainer, researcher or coordinator | Researcher, advisory source, database, internal tool |
| Tied to a release | Yes | Yes, per product and version | No |
| Completeness claim | Yes | No | No |
analysis block | Expected on every entry | Required; it is the point | Usually absent |
ratings | Source ratings plus product context ratings | Optional | Source ratings, often several |
affects | Components or services in this product | Products and versions the statement covers | Packages and version ranges |
| Unknown or unpublished vulnerabilities | Yes, with supplier identifiers | Possible with supplier identifiers in CycloneDX | Yes, common for research findings |
How has the CycloneDX vulnerability object changed by version?
The vulnerabilities array is not as old as CycloneDX. This timeline uses GitHub release dates from the CycloneDX specification repository and the Ecma International page for ECMA-424, and field details read directly from each published JSON schema.
| Version | Release date | What changed for vulnerability data |
|---|---|---|
| 1.3 and earlier | 2021 and before | No vulnerabilities array in the core schema |
| 1.4 | 12 January 2022 | Adds top level vulnerabilities with 18 properties, including ratings, affects, analysis (state, justification, response, detail), credits and tools. Score methods CVSSv2, CVSSv3, CVSSv31, OWASP and other |
| 1.5 | 26 June 2023 | Adds workaround, proofOfConcept, rejected, analysis.firstIssued, analysis.lastUpdated, and compositions[].vulnerabilities for declaring completeness. The schema file in the repository today also lists CVSSv4 and SSVC as rating methods |
| 1.6 | 9 April 2024 | Same 21 vulnerability properties and enumerations as 1.5 |
| ECMA-424, 1st edition | June 2024 | Ecma International standard based on CycloneDX 1.6 |
| 1.7 | 21 October 2025 | Same 21 vulnerability properties and enumerations; the CycloneDX CLI validates against 1.7 by default |
| ECMA-424, 2nd edition | December 2025 | Defines CycloneDX 1.7, including vulnerabilities, per the Ecma page |
The practical upshot: if a supplier sends you a VDR with specVersion 1.4, it cannot use workaround or declare completeness through compositions. Ask for 1.5 or later. Anything from 1.5 to 1.7 carries the same vulnerability fields, so the version choice is driven by the rest of the BOM, not by VDR needs.
Which fields matter in a VDR, VEX or BOV?
Every vulnerability object lives in the top level vulnerabilities array of a CycloneDX BOM. In the 1.7 schema it has 21 properties. This table shows each one and how the three documents use it.
| Field | What it holds | VDR | VEX | BOV |
|---|---|---|---|---|
bom-ref | Entry identifier, used by compositions and BOM-Link | Recommended | Optional | Optional |
id | CVE or supplier assigned ID | Required in practice | Required in practice | Required in practice |
source | Publisher name and URL | Yes | Yes | Yes |
references | Other identifiers, such as a GHSA | Yes | Optional | Yes |
ratings | Severity or risk ratings | Yes, including product context | Optional | Yes, often several sources |
cwes | CWE numbers | Yes | Rare | Yes |
description, detail | What the vulnerability is | Yes | Rare | Yes |
recommendation | What the reader should do | Yes | Optional | Yes |
workaround | Temporary bypass (1.5+) | When one exists | When one exists | When one exists |
proofOfConcept | Reproduction material (1.5+) | Rarely shared externally | No | Sometimes, between trusted parties |
advisories | Published advisory links | Yes | Optional | Yes |
created, published, updated, rejected | Record timestamps | Yes | Optional | Yes |
credits | Discovery credit | When known | No | Yes |
tools | Tools that found or scored it | Optional | Optional | Optional |
analysis | Impact in product context | Yes, every entry | Yes, the core | Usually absent |
affects | Affected components, versions, ranges | Yes | Yes | Yes |
properties | Name and value extensions | Tool specific | Tool specific | Tool specific |
Four of these carry most of the meaning.
ratings
Each rating has source, score, severity, method, vector and justification. Severity is one of critical, high, medium, low, info, none or unknown. Method is one of CVSSv2, CVSSv3, CVSSv31, CVSSv4, OWASP, SSVC or other. The schema description tells consumers that source ratings may differ and that they should consider ratings when prioritising.
Multiple ratings are what let a VDR carry product context. A supplier keeps the NVD base score and adds its own rating using environmental CVSS metrics or an OWASP risk rating, with justification explaining the difference. That is the "risk transparency" the CycloneDX comparison credits to VDRs. A BOV uses the same field for ratings from several independent sources.
affects
Each entry has a required ref and an optional versions list. The ref is either the bom-ref of a component or service in the same BOM, or a BOM-Link that points into another BOM, in the form urn:cdx:serialNumber/version#bom-ref. Each versions item has either a single version or a range in Package URL version range syntax (vers), plus a status of affected, unaffected or unknown.
That is why a VDR or VEX can live apart from the SBOM and still point at exact components, and why a BOV can describe "all lodash versions below 4.17.21" without naming a product. Linking is covered in depth in CycloneDX BOM-Link explained.
analysis
analysis holds state, justification, response, detail, firstIssued and lastUpdated.
state values and their schema definitions:
CycloneDX state | Meaning in the schema | Closest CISA VEX status |
|---|---|---|
resolved | The vulnerability has been remediated | fixed |
resolved_with_pedigree | Remediated, with evidence such as commit history or diffs in the component's pedigree | fixed |
exploitable | May be directly or indirectly exploitable | affected |
in_triage | Being investigated | under_investigation |
false_positive | Not specific to the component and falsely associated | not_affected (no exact match) |
not_affected | The component or service is not affected; a justification should be given | not_affected |
The mapping column is our reading of the two documents, not an official crosswalk. The CycloneDX schema and the CISA minimum requirements do not publish one in the pages cited here.
justification applies to not_affected and has nine values in CycloneDX: 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. CISA's minimum requirements list five: component not present, vulnerable code not present, vulnerable code not in execute path, vulnerable code cannot be controlled by adversary, and inline mitigations already exist. Some pairs line up (code_not_reachable and "not in execute path"); others, such as protected_at_perimeter, have no clean equivalent, which matters when converting.
response is an array: can_not_fix, will_not_fix, update, rollback and workaround_available. The schema says responses are strongly encouraged when the state is exploitable. In a VDR this is where the "plans to address" part lives, and can_not_fix or will_not_fix are honest answers a buyer needs to see.
detail is free text. The schema says that when a vulnerability is not exploitable, this field should explain specifically why. A justification code alone is a claim; the detail is the evidence.
compositions and the completeness claim
The CycloneDX VDR page says the format can represent "the known completeness of vulnerability intelligence." The mechanism, from 1.5 onward, is the compositions array. Each composition has an aggregate value and can list vulnerabilities by their bom-ref. Aggregate values include complete, incomplete, incomplete_first_party_only, incomplete_third_party_only, their open source and proprietary variants, unknown and not_specified.
A VDR makes its completeness promise machine readable by listing every vulnerability bom-ref under a composition whose aggregate honestly describes coverage. If first party code review is unfinished, incomplete_first_party_only says so, which is more useful than an unqualified complete.
What does a minimal VDR look like?
This VDR covers release 3.2.0 of a fictional product, example-portal. It reports one inherited CVE that the supplier has analysed as not reachable, and one previously unknown first party vulnerability with a supplier assigned identifier. It validates against the CycloneDX 1.7 JSON schema (bom-1.7.schema.json); we checked it with a JSON Schema draft 7 validator against the published schema files.
{
"$schema": "http://cyclonedx.org/schema/bom-1.7.schema.json",
"bomFormat": "CycloneDX",
"specVersion": "1.7",
"serialNumber": "urn:uuid:7f1c2a4e-5b3d-4c8e-9a10-2b6d8e4f1a77",
"version": 1,
"metadata": {
"timestamp": "2026-10-24T09:00:00Z",
"component": {
"type": "application",
"bom-ref": "example-portal@3.2.0",
"name": "example-portal",
"version": "3.2.0"
}
},
"components": [
{
"type": "library",
"bom-ref": "pkg:npm/lodash@4.17.20",
"name": "lodash",
"version": "4.17.20",
"purl": "pkg:npm/lodash@4.17.20"
}
],
"vulnerabilities": [
{
"bom-ref": "vuln-cve-2021-23337",
"id": "CVE-2021-23337",
"source": {
"name": "NVD",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2021-23337"
},
"references": [
{
"id": "GHSA-35jh-r3h4-6jhm",
"source": {
"name": "GitHub Advisory Database",
"url": "https://github.com/advisories/GHSA-35jh-r3h4-6jhm"
}
}
],
"ratings": [
{
"source": { "name": "NVD" },
"score": 7.2,
"severity": "high",
"method": "CVSSv31",
"vector": "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H"
}
],
"cwes": [77, 94],
"description": "Command injection through the template function in lodash before 4.17.21.",
"recommendation": "Upgrade lodash to 4.17.21 or later.",
"published": "2021-02-15T13:15:00Z",
"analysis": {
"state": "not_affected",
"justification": "code_not_reachable",
"response": ["update"],
"detail": "example-portal never calls lodash template(). lodash moves to 4.17.21 in release 3.2.1.",
"firstIssued": "2026-10-24T09:00:00Z",
"lastUpdated": "2026-10-24T09:00:00Z"
},
"affects": [
{
"ref": "pkg:npm/lodash@4.17.20",
"versions": [{ "version": "4.17.20", "status": "affected" }]
}
]
},
{
"bom-ref": "vuln-example-2026-0007",
"id": "EXAMPLE-2026-0007",
"source": { "name": "Example PSIRT", "url": "https://example.com/security" },
"ratings": [
{ "source": { "name": "Example PSIRT" }, "severity": "medium", "method": "other" }
],
"cwes": [639],
"description": "Missing ownership check on the report export endpoint lets a signed in user read another tenant's export.",
"recommendation": "Upgrade example-portal to 3.2.1.",
"credits": {
"individuals": [{ "name": "A. Researcher" }]
},
"published": "2026-10-24T09:00:00Z",
"analysis": {
"state": "exploitable",
"response": ["update"],
"detail": "Fixed in 3.2.1. No workaround short of disabling report export.",
"firstIssued": "2026-10-24T09:00:00Z",
"lastUpdated": "2026-10-24T09:00:00Z"
},
"affects": [
{
"ref": "example-portal@3.2.0",
"versions": [
{ "version": "3.2.0", "status": "affected" },
{ "version": "3.2.1", "status": "unaffected" }
]
}
]
}
],
"compositions": [
{
"aggregate": "complete",
"vulnerabilities": ["vuln-cve-2021-23337", "vuln-example-2026-0007"]
}
]
}
Things to notice:
- The CVE details (7.2, vector, CWE-77 and CWE-94, fixed in 4.17.21) match advisory GHSA-35jh-r3h4-6jhm in the OSV database. Check your own sources before reuse.
- The lodash entry is
not_affectedyet setsresponsetoupdate: "this does not affect you, and we are removing it anyway," which helps a buyer whose scanner keeps flagging 4.17.20. - The second entry is a first party flaw with no CVE, the "previously unknown" case that sets VDRs apart. Its
affectspoints at the product's ownbom-refand records 3.2.1 as unaffected. compositionsdeclares the list complete. Only do that if it is true.
What does a minimal BOV look like?
This BOV carries one vulnerability record about the lodash package in general, with no product, no analysis and a version range. It validates against the CycloneDX 1.7 JSON schema.
{
"$schema": "http://cyclonedx.org/schema/bom-1.7.schema.json",
"bomFormat": "CycloneDX",
"specVersion": "1.7",
"serialNumber": "urn:uuid:0c5e9b1a-8d42-4f3e-b7a6-91d2c4e8f310",
"version": 1,
"metadata": { "timestamp": "2026-10-24T09:00:00Z" },
"components": [
{
"type": "library",
"bom-ref": "pkg:npm/lodash",
"name": "lodash",
"purl": "pkg:npm/lodash"
}
],
"vulnerabilities": [
{
"id": "CVE-2021-23337",
"source": { "name": "NVD", "url": "https://nvd.nist.gov/vuln/detail/CVE-2021-23337" },
"references": [
{
"id": "GHSA-35jh-r3h4-6jhm",
"source": { "name": "GitHub Advisory Database", "url": "https://github.com/advisories/GHSA-35jh-r3h4-6jhm" }
}
],
"ratings": [
{
"source": { "name": "NVD" },
"score": 7.2,
"severity": "high",
"method": "CVSSv31",
"vector": "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H"
}
],
"cwes": [77, 94],
"description": "Command injection through the template function in lodash before 4.17.21.",
"recommendation": "Upgrade to 4.17.21 or later.",
"advisories": [
{ "title": "Command Injection in lodash", "url": "https://github.com/advisories/GHSA-35jh-r3h4-6jhm" }
],
"published": "2021-02-15T13:15:00Z",
"affects": [
{
"ref": "pkg:npm/lodash",
"versions": [
{ "range": "vers:npm/<4.17.21", "status": "affected" },
{ "version": "4.17.21", "status": "unaffected" }
]
}
]
}
]
}
The component here has no version, only a package URL without one, because the BOV describes the package across versions. The range uses the vers syntax the schema references. A receiving tool matches that range against its own inventory; the BOV itself never claims any product is affected.
What does the matching VEX look like?
Here is a standalone CycloneDX VEX for the same release. Instead of embedding components, it points into the SBOM with a BOM-Link built from that SBOM's serialNumber, version 1, and the component's bom-ref. It validates against the CycloneDX 1.7 JSON schema.
{
"$schema": "http://cyclonedx.org/schema/bom-1.7.schema.json",
"bomFormat": "CycloneDX",
"specVersion": "1.7",
"serialNumber": "urn:uuid:a3b1e0d2-6c7f-4f81-8e2a-5d9c0b7e4f12",
"version": 1,
"metadata": { "timestamp": "2026-10-24T09:00:00Z" },
"vulnerabilities": [
{
"id": "CVE-2021-23337",
"source": { "name": "NVD", "url": "https://nvd.nist.gov/vuln/detail/CVE-2021-23337" },
"analysis": {
"state": "not_affected",
"justification": "code_not_reachable",
"response": ["update"],
"detail": "example-portal 3.2.0 never calls lodash template().",
"firstIssued": "2026-10-24T09:00:00Z",
"lastUpdated": "2026-10-24T09:00:00Z"
},
"affects": [
{ "ref": "urn:cdx:7f1c2a4e-5b3d-4c8e-9a10-2b6d8e4f1a77/1#pkg:npm/lodash@4.17.20" }
]
}
]
}
The VEX leaves out the first party vulnerability, ratings, CWEs and completeness claim. It answers a smaller question: a buyer holding only this file could suppress the lodash finding for 3.2.0 and would know nothing about the tenant isolation bug.
How do you produce a VDR and a matching VEX for a release?
The walkthrough below uses open source tools from the CycloneDX organisation, OWASP and OpenVEX, plus Sigstore cosign for signing. Tool flags come from each tool's own README or documentation; the jq glue is ours. Adjust paths to your project.
Step 1: generate the SBOM for the release
cdxgen, from the CycloneDX organisation, generates CycloneDX SBOMs for many ecosystems. Run it from the repository root at the release tag.
git checkout v3.2.0
npm install -g @cyclonedx/cdxgen
cdxgen -o bom.json .
For a container image, cdxgen's README shows the image form:
cdxgen ghcr.io/example/example-portal:3.2.0 -o bom.json -t docker
Record the SBOM's serialNumber and version. Every BOM-Link in the VEX depends on them.
jq -r '.serialNumber, .version' bom.json
Step 2: validate the SBOM
The CycloneDX CLI validates against a chosen specification version and can fail a build on errors. Its README lists versions 1.0 through 1.7, defaulting to 1.7.
brew install cyclonedx/cyclonedx/cyclonedx-cli
cyclonedx-cli validate --input-file bom.json --input-version v1_7 --fail-on-errors
The CLI also publishes a Docker image (cyclonedx/cyclonedx-cli) if you prefer not to install it on build agents.
Step 3: generate a draft VDR from the SBOM
The cdxgen README says to pair it with OWASP dep-scan to automate VDR generation. dep-scan's own documentation describes its VDR as a CycloneDX document that attaches vulnerability data to the components of a BOM, written next to the BOM in the reports directory.
pip install owasp-depscan
depscan --src $PWD --reports-dir $PWD/reports
ls reports/*.vdr.json
dep-scan's VDR guide says entries carry states such as in_triage, the dependency path in analysis.detail, and its prioritisation verdicts as properties named depscan:prioritized and depscan:insights. Treat this as a draft: a machine match is a scan report until a person signs off on the analysis.
Step 4: triage every entry
This is the step that turns a scan into a VDR. For each vulnerability, a person or a reviewed rule decides:
- Does it affect us? Set
statetoexploitable,not_affected,false_positive,resolvedor keepin_triage. - Why? For
not_affected, pick ajustificationand writedetailthat someone outside your team could check. - What will we do? Set
responseandrecommendation. - Does severity change in our context? Add a second rating from your own source with a
justification.
A quick way to see what still needs review:
jq -r '.vulnerabilities[]
| select(.analysis.state == null or .analysis.state == "in_triage")
| [.id, (.affects[0].ref // "")] | @tsv' reports/*.vdr.json
Then add any first party vulnerabilities your product security team is tracking. These will not come from any scanner. Give each a supplier identifier, a source naming your security team, and credits if a researcher reported it.
Step 5: declare completeness honestly
Add a compositions entry listing every vulnerability bom-ref with an aggregate that matches what you actually reviewed.
DRAFT=$(ls reports/*.vdr.json | head -1)
jq '.compositions = [{
"aggregate": "complete",
"vulnerabilities": [.vulnerabilities[]."bom-ref"]
}]' "$DRAFT" > vdr.json
If some entries lack a bom-ref, add them first; a composition can only reference entries that have one. Use incomplete_first_party_only or another incomplete value if first party review is not finished.
Step 6: validate the VDR
cyclonedx-cli validate --input-file vdr.json --input-version v1_7 --fail-on-errors
Schema validation proves structure, not truth. It will accept a not_affected with an empty detail. Add your own checks:
jq -e '[.vulnerabilities[]
| select(.analysis.state == "not_affected")
| select((.analysis.justification // "") == "" or (.analysis.detail // "") == "")]
| length == 0' vdr.json
That command exits non zero if any not_affected entry lacks a justification or a detail.
Step 7: derive the matching VEX
There are two common routes.
CycloneDX VEX. Extract the analysed entries into a separate BOM and rewrite each affects.ref as a BOM-Link into the SBOM.
SERIAL=$(jq -r '.serialNumber | sub("^urn:uuid:"; "")' bom.json)
VER=$(jq -r '.version' bom.json)
NEWID=$(uuidgen | tr 'A-Z' 'a-z')
jq --arg s "$SERIAL" --arg v "$VER" --arg id "$NEWID" '{
"bomFormat": "CycloneDX",
"specVersion": "1.7",
"serialNumber": ("urn:uuid:" + $id),
"version": 1,
"vulnerabilities": [.vulnerabilities[]
| select(.analysis.state != null)
| {id, source, analysis,
affects: [.affects[] | {ref: ("urn:cdx:" + $s + "/" + $v + "#" + .ref)}]}]
}' vdr.json > vex.json
Each VEX gets its own fresh serial number. BOM-Links only work if the referenced bom-ref values exist in the SBOM, so this step assumes your VDR's affects.ref values are the SBOM's bom-ref values, which is the case when the VDR was produced from that SBOM. Entries that point at the product itself (first party issues) need the product's bom-ref from the SBOM's metadata. Validate the result with the same CLI command as before.
OpenVEX. If your customers' tools prefer OpenVEX, vexctl from the OpenVEX project creates statements directly. This example follows the form in the vexctl README:
go install github.com/openvex/vexctl@latest
vexctl create \
--product="pkg:npm/example-portal@3.2.0" \
--vuln="CVE-2021-23337" \
--status="not_affected" \
--justification="vulnerable_code_not_in_execute_path" > vex.openvex.json
OpenVEX uses the CISA style justification names, so map CycloneDX's code_not_reachable to vulnerable_code_not_in_execute_path when you convert. vexctl merge can combine statements across documents.
dep-scan also documents a CSAF option that writes an OASIS CSAF VEX document, if your customers expect CSAF.
Step 8: sign and publish
The 2022 NIST text asked for a signed, timestamped VDR. Sigstore cosign signs the file as a blob and writes a bundle with the signature and transparency log proof:
cosign sign-blob vdr.json --bundle vdr.json.sigstore.json
cosign sign-blob vex.json --bundle vex.json.sigstore.json
Consumers verify with cosign verify-blob and the bundle, pinning the expected signer identity and OIDC issuer. Publish the SBOM, VDR, VEX and bundles together under the release, and keep the URLs stable so customers can poll them.
Step 9: put it in CI
A minimal GitHub Actions job that rebuilds the draft VDR on every tag and refuses to publish an invalid one:
name: vdr
on:
push:
tags: ["v*"]
jobs:
vdr:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: npm install -g @cyclonedx/cdxgen
- run: pip install owasp-depscan
- run: cdxgen -o bom.json .
- run: depscan --src $PWD --reports-dir $PWD/reports
- run: |
docker run --rm -v "$PWD:/w" cyclonedx/cyclonedx-cli \
validate --input-file /w/bom.json --input-version v1_7 --fail-on-errors
- uses: actions/upload-artifact@v4
with:
name: draft-vdr
path: |
bom.json
reports/
The job stops at a draft on purpose. Triage (step 4) needs a human or a reviewed rule set, and the final VDR is a statement your company makes to customers.
Step 10: keep it current
A VDR is only true on the day it is issued. Re-match the SBOM against vulnerability sources on a schedule, such as daily, and bump the VDR's version and each changed entry's analysis.lastUpdated when anything changes. For urgent issues between updates, a security advisory page or mailing list serves as the separate notification channel the 2022 NIST text suggested.
When should a buyer ask for a VDR rather than a VEX?
Ask for a VDR when you need the supplier's full position on risk:
- The software will hold sensitive data or run in a critical path, and you want every known vulnerability, including ones in the supplier's code with no CVE yet.
- You do not scan SBOMs yourself, so a VEX that suppresses false positives has nothing to suppress.
- Your contract or policy is written against the 2022 text of NIST SP 800-161r1, or a federal flow down that uses it.
- You want to see
can_not_fixandwill_not_fixdecisions written down, not discovered later. - You need modified severity in context, such as a supplier telling you a critical base score drops to low in their deployment model, with reasons.
Ask for VEX when you already have the inventory and the matching:
- You receive SBOMs from the supplier and scan them yourself, and the problem is noise.
- You need statements across a product family or many versions at once, which VEX scopes allow.
- Your tooling consumes CSAF or OpenVEX and does not read CycloneDX VDRs.
Ask for both when the software matters and you scan it yourself. The VDR gives the complete picture at release; VEX documents keep your scanners quiet between releases.
A BOV is rarely something a buyer asks a supplier for. It is what you might ask a threat intelligence provider, an internal research team or a coordinator for when you want vulnerability records in the same format as your SBOMs.
Wording to put in a contract or questionnaire, adapted to your needs:
For each release, the supplier will provide a CycloneDX (1.5 or later) SBOM and a Vulnerability Disclosure Report listing all known vulnerabilities affecting the release, including vulnerabilities in supplier developed code, each with an impact analysis, justification where not affected, and planned response. The VDR will declare its completeness using CycloneDX compositions, be updated within an agreed number of days of a new vulnerability affecting the release, and be signed.
The live post which document answers which buyer question: SBOM, VEX or report covers the broader buyer question set.
Where Safeguard fits, and where it does not
Safeguard does not author VDR or BOV documents today, and it does not convert between vulnerability document formats. What it does sits on either side of them. On the supplier side, SBOM generation and analysis (generally available) produces SPDX and CycloneDX SBOMs, with continuous scanning on push, on a schedule or on release, and matches components against vulnerability sources with reachability analysis to help with the triage in step 4. Policies and gates (generally available) can block a release when findings break your rules. On the buyer side, CSAF and VEX upload (generally available) lets you bring a supplier's VEX into the platform, TPRM (generally available) lets you onboard vendors and request SBOMs, projects or products from them, and the Portal (generally available) is the surface for publishing and sharing SBOMs, with the Trust Center itself in early access. If you need a signed VDR file for a customer, the open source steps above are the way to make it; Safeguard helps you keep the inventory and the fixes behind it current.
Common mistakes
- Calling a raw scan export a VDR. A tool's output with every entry
in_triageis a scan report. Without analysis, justification and a response per entry, it fails the basic definition. - Claiming completeness you have not earned. An aggregate of
completeon a VDR that never looked at first party code is a false statement. Use the incomplete variants. not_affectedwith no detail. A justification code alone is an assertion. CISA requires an impact statement when no justification is given, and the CycloneDX schema says the detail should explain why. Write what someone could verify.- Citing NIST SP 800-161r1 without checking the revision. The 1 November 2024 update replaced the VDR language with vulnerability advisory reports under ISO/IEC 29147. Quote the version your requirement was written against.
- Shipping a VDR on CycloneDX 1.4. It cannot carry
workaround,proofOfConceptor completeness throughcompositions. Use 1.5 or later. - Breaking BOM-Links. A separate VEX or VDR that points at
urn:cdx:serial/version#refbreaks silently when the SBOM is regenerated with a new serial number or differentbom-refvalues. Generate them together and keepbom-refvalues stable, such as package URLs. - Treating a BOV as a statement about your product. A BOV that lists a vulnerable range for a library says nothing about whether your build includes it. Match it against an SBOM first.
- Converting justification codes one to one. CycloneDX has nine justifications, CISA five. Some have no clean equivalent. Keep the
detailtext when converting so meaning survives. - Publishing once and never updating. A VDR whose
versionandanalysis.lastUpdatednever move is a snapshot dressed up as a living report.
FAQ
What is the difference between a VDR and a VEX?
A VDR lists all known vulnerabilities affecting a product, with impact analysis and the supplier's planned response for each, and makes a completeness claim. VEX states exploitability status (not affected, affected, fixed, under investigation) for specific vulnerabilities and products, and makes no claim that the list is complete.
Is a VDR required by NIST?
NIST SP 800-161r1 is guidance, not a regulation, and its 2022 text said enterprises "may consider" providing a VDR. The 1 November 2024 update replaced that wording with vulnerability advisory reports under ISO/IEC 29147. A VDR can still be required by a contract or by an agency that flows the guidance down.
What is a Bill of Vulnerabilities in CycloneDX?
A BOV is a CycloneDX document used to exchange vulnerability intelligence between systems, teams or organisations. It carries vulnerability records with ratings, credits, advisories and affected versions, usually without a product subject or product specific analysis.
Which CycloneDX version supports VDR?
Version 1.4, released in January 2022, introduced the vulnerabilities array. Version 1.5 added fields that a complete VDR needs, including workaround and vulnerability completeness through compositions. Versions 1.6 and 1.7 keep the same vulnerability fields.
Can SPDX carry a VDR?
VDR and BOV are CycloneDX capability names, and the CycloneDX sources used for this guide do not describe an SPDX equivalent. This series compares SPDX 3's security model with CSAF, OpenVEX and CycloneDX in its VEX formats post.
Does a VDR include vulnerabilities that do not affect the product?
Often it should. A matched vulnerability marked not_affected with a justification shows customers you checked, so they do not chase the same scanner finding.
Should a VDR be embedded in the SBOM or published separately?
Both work in CycloneDX. The CycloneDX examples recommend separating vulnerability data from the BOM because inventory changes rarely while vulnerability status changes often, and support embedding for point in time audit snapshots.
How often should a VDR be updated?
Whenever a new vulnerability affecting the release appears or an analysis decision changes. In practice, re-match the SBOM daily and reissue with a new document version on any change.
Fix it without waiting for upstream
A VDR is a list of problems with a plan attached; it fixes nothing on its own, and every update response in it is a promise that someone still has to ship a rebuilt release. Where the vulnerable piece is an open source dependency or container image you build yourself, closing that gap is supply chain hygiene, and that is the part Safeguard helps with.
Whatever the vulnerability, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.
1. Autonomous remediation: pull requests, not tickets
When a fixed version exists, Safeguard's Griffin models open a tested, policy gated pull request that moves the affected dependency to the safe version. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation covers eight categories, including dependencies, licenses, security posture and findings. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Autonomous remediation ships switched off, with every category set to Manual, so your team decides how much runs on its own and what still needs a human approval.
2. Gold Open Source: the safe version in seconds, free
gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds. The safeguard-sh/gold-scan-action GitHub Action can fail a build on a known exploited vulnerability, on any known vulnerability, or never, so a version you have just removed cannot quietly come back in next week's merge.
3. Gold Registry: start from components that are already fixed
registry.safeguard.sh provides hardened, drop in replacements for upstream packages and container images. Every Gold artifact is held continuously to zero exploitable critical or high CVEs, zero malware and a completed license check, with SLSA L3 or higher provenance, a Sigstore signature and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.
When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened, compatibility tested build of a specific package. This fork and patch flow is in early access today, so ask which packages it covers before you plan around it.
| Your situation | What to use |
|---|---|
| A fixed version exists | Autonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version |
| You need to know today whether you are affected | Gold Open Source lookup and "Am I affected?" checker, free and without login |
| You want vulnerable versions to stop shipping | The Gold scan GitHub Action in CI, plus Safeguard policies and gates |
| Upstream has no fix yet, or will not ship one | A custom Gold request; fork and patch (early access) |
| You want to start clean on new projects | Pull hardened components and images from Gold Registry |
One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.
See how Safeguard would help
These demos do not produce a VDR. They show the work that makes a VDR's update responses come true: finding the vulnerable dependency, fixing it with approval, and scanning again. You can download Safeguard or read the CLI documentation to try the same flow on your own release.
The first demo is a real CLI session. You ask the assistant to fix the lodash CVE in a repository; it reads the finding and the advisory, asks before each command, edits package.json to lodash 4.17.21, runs npm install and npm test with your approval, prints a summary and pushes nothing. That is the same lodash entry as the VDR example above, moving from response: update to a shipped fix.
Click through it at your own pace, or open it full screen.
The second demo sets up the Safeguard runner on your own machine, approves it with its device code, and then scans from the terminal. For a supplier preparing a VDR, this is how the release's dependencies get matched again on the machine that builds them, before triage starts.
Click through it at your own pace, or open it full screen.
The third demo connects Claude to the Safeguard MCP server with OAuth and lets you choose which tools Claude can use. A product security reviewer can then ask about findings for a project while drafting the analysis text for each VDR entry, with access limited to the tools you allowed.
Click through it at your own pace, or open it full screen.
Quick check
A supplier sends a CycloneDX file in which every vulnerability has analysis state in_triage and there is no compositions entry. Is it a VDR?
Not yet. It is a draft or a scan report. A VDR needs an impact analysis and planned response for each vulnerability, and should state how complete it is.
Which document would you ask a threat intelligence source for if you want vulnerability records in the same format as your SBOMs?
A CycloneDX Bill of Vulnerabilities (BOV). It carries vulnerability records, ratings and affected version ranges without claiming anything about a specific product.
Sources
- CycloneDX: Vulnerability Disclosure Report (VDR) capability
- CycloneDX: Vulnerability Exploitability eXchange (VEX) capability
- CycloneDX: Bill of Vulnerabilities (BOV) capability
- CycloneDX: use cases
- CycloneDX specification repository, JSON schemas 1.3 to 1.7
- CycloneDX specification releases
- CycloneDX bom-examples: VDR
- CycloneDX bom-examples: VEX
- Ecma International: ECMA-424 CycloneDX Bill of materials specification
- NIST SP 800-161 Rev. 1 (May 2022 original text)
- NIST SP 800-161 Rev. 1, update 1 (includes updates as of 1 November 2024)
- CISA: Minimum Requirements for Vulnerability Exploitability eXchange (VEX), April 2023
- CISA: When to Issue VEX Information, November 2023
- CISA: Software Bill of Materials
- CycloneDX CLI README
- cdxgen README
- OWASP dep-scan README
- OWASP dep-scan: VDR guide for analysts
- OpenVEX vexctl README
- Sigstore: signing blobs with cosign
- OSV: GHSA-35jh-r3h4-6jhm (CVE-2021-23337)
Last reviewed: 24 October 2026. Specifications and guidance change; check the linked sources for the current versions.