diff --git a/package-lock.json b/package-lock.json index 3cdc6ee..5e96ccd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "@document-writing-tools/kernux-theme": "2.1.0-rc.2", "@ltonetwork/http-message-signatures": "^0.1.12", + "@radix-ui/react-collapsible": "1.1.13", "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-separator": "^1.1.8", "@radix-ui/react-slot": "^1.2.4", @@ -32,6 +33,7 @@ "next": "^16.2.6", "next-sitemap": "^4.2.3", "ogl": "^1.0.11", + "openapi-types": "12.1.3", "radix-ui": "^1.4.3", "react": "^19.2.6", "react-dom": "^19.2.6", @@ -3586,6 +3588,36 @@ } } }, + "node_modules/@radix-ui/react-accordion/node_modules/@radix-ui/react-collapsible": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.12.tgz", + "integrity": "sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-alert-dialog": { "version": "1.1.15", "resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.15.tgz", @@ -3736,19 +3768,96 @@ } }, "node_modules/@radix-ui/react-collapsible": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.12.tgz", - "integrity": "sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.13.tgz", + "integrity": "sha512-F0s8+p2XNpfc3k02zBfB0jPWbkHVG162+p7BdUMyJ2308QMqZ+oaclX+FAzKFovgL5OqRU+Rvy6f/vbdlJVaqA==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-controllable-state": "1.2.2", - "@radix-ui/react-use-layout-effect": "1.1.1" + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-id": "1.1.2", + "@radix-ui/react-presence": "1.1.6", + "@radix-ui/react-primitive": "2.1.5", + "@radix-ui/react-use-controllable-state": "1.2.3", + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collapsible/node_modules/@radix-ui/primitive": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.4.tgz", + "integrity": "sha512-7AdCK9PQyiljKoBDbN8OuctCbd/esdwZPQ8RtOE3SsyQtUpiPb+ND75q0jEhC1m1ecBI0MFNeLJvwIh9iKHRcQ==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-collapsible/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.3.tgz", + "integrity": "sha512-rYOP8OMnuuPMQF1uhPVlGNcCDlkokKqGFE3JcxFViIkAXP7EvFWUliJAstrapypaBLJNHbZL6jGhbVDGTwmVhA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collapsible/node_modules/@radix-ui/react-context": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.4.tgz", + "integrity": "sha512-QwH4PO5urrbO+FaGd5Aglg+YJgWTyyuZ3g/6mKvsqraLkglDdckw9JafgL5McL5VEJ6EPNduPaT3ZE9BttDAqg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collapsible/node_modules/@radix-ui/react-id": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.2.tgz", + "integrity": "sha512-orBC88futVpqCmhX1p4cvquNHsELQ+w+vBJnuj3ftETI5bJb0bZn3Tqu3SWN2IOcPycTnMGnhwoermvISt72sA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collapsible/node_modules/@radix-ui/react-presence": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.6.tgz", + "integrity": "sha512-zdTk4PlUO0E18HnZ3wYbW0KkJJxWCdiNYp6g6X1PtONFhxVkg01vliTJAmwIszU6mHiyBOoW9P0rAugl5/hULQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.2" }, "peerDependencies": { "@types/react": "*", @@ -3765,6 +3874,99 @@ } } }, + "node_modules/@radix-ui/react-collapsible/node_modules/@radix-ui/react-primitive": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.5.tgz", + "integrity": "sha512-zifXeB8Y88qCYx8PLZ5oQb32KwZub+s925mMoZsBBq9KUQqWKkREubTfs6ASjRPPBe7Jt9O8OHH89+95VG+grA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.5" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collapsible/node_modules/@radix-ui/react-slot": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.5.tgz", + "integrity": "sha512-rCMO3QsIVKv5JTY5CVbo2MvO77SpEqqYc8AvRE7OWqRDOIqAKjsp+DrmnY9uc8NPdxB5E2z47HTYGeE2+NTptg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collapsible/node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.3.tgz", + "integrity": "sha512-PLzC90MS+ReootmjC597dvopoelpZ8Q61HJkDXZSExitIq7PL55vHNnesAHwguHK0aPfBnpdNzQtv1uliaqQrA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.3", + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collapsible/node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.3.tgz", + "integrity": "sha512-6c8ZqvPTWILEKnyVkP53EGRCcpnJiKTC21sS/6R1GF5xKyHJJWQEPfkqlcgUkdRQivd6tb23abUwe4ngWmY0JA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collapsible/node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.2.tgz", + "integrity": "sha512-jrBWOxZITuGcnjRCM2t2U5ZPkCLxD+Ym6DjfssS5haTj2iiak/DOb64JeN6OdLfLgptb6/e2kKR+ZuTrGoZTPA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-collection": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", @@ -15998,6 +16200,12 @@ "node": ">=12.20.0" } }, + "node_modules/openapi-types": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", + "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", + "license": "MIT" + }, "node_modules/openid-client": { "version": "6.8.4", "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-6.8.4.tgz", @@ -16722,6 +16930,36 @@ } } }, + "node_modules/radix-ui/node_modules/@radix-ui/react-collapsible": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.12.tgz", + "integrity": "sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/radix-ui/node_modules/@radix-ui/react-label": { "version": "2.1.7", "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.7.tgz", diff --git a/src/pages/explanations/vulnerability-management/_meta.ts b/src/pages/explanations/vulnerability-management/_meta.ts index 4ac8162..177062e 100644 --- a/src/pages/explanations/vulnerability-management/_meta.ts +++ b/src/pages/explanations/vulnerability-management/_meta.ts @@ -17,6 +17,9 @@ export default { 'mitigation-strategies': { title: 'Mitigation Strategies & VEX Rules', }, + 'what-is-vex': { + title: 'What is VEX', + }, 'false-positive-detection': { title: 'False Positives', }, diff --git a/src/pages/explanations/vulnerability-management/vulnerability-management-overview.mdx b/src/pages/explanations/vulnerability-management/vulnerability-management-overview.mdx index 374a9a6..ae316fb 100644 --- a/src/pages/explanations/vulnerability-management/vulnerability-management-overview.mdx +++ b/src/pages/explanations/vulnerability-management/vulnerability-management-overview.mdx @@ -142,6 +142,7 @@ The result is that vulnerability management — and the evidence required to pro - [Risk Assessment Methodology](/explanations/vulnerability-management/risk-assessment-methodology/) — how DevGuard combines CVSS, EPSS, and context - [Vulnerability Matching](/explanations/vulnerability-management/vulnerability-matching/) — how findings are mapped to CVEs - [Mitigation Strategies](/explanations/vulnerability-management/mitigation-strategies/) — patching, dependency upgrades, VEX, and compensating controls +- [What is VEX](/explanations/vulnerability-management/what-is-vex/) — exchanging exploitability information for vulnerabilities - [False Positives](/explanations/vulnerability-management/false-positive-detection/) — handling noise without losing real risks - [DevGuard & Compliance Frameworks](/explanations/compliance/why-compliance-matters/) — mapping vulnerability management to CRA, NIS2, ISO 27001 - [CRA Compliance with DevGuard](https://devguard.org/cra_compliance) — how DevGuard maps to Cyber Resilience Act requirements diff --git a/src/pages/explanations/vulnerability-management/what-is-vex.mdx b/src/pages/explanations/vulnerability-management/what-is-vex.mdx new file mode 100644 index 0000000..34d838f --- /dev/null +++ b/src/pages/explanations/vulnerability-management/what-is-vex.mdx @@ -0,0 +1,101 @@ +--- +title: What is a VEX Report +description: "Understand the VEX standard for vulnerabilities — the problem it solves, its status values and justifications, and the CSAF, CycloneDX, and OpenVEX formats." +seo: + robots: index,follow + og: + image: /og-image.png + type: article + schema: + type: TechArticle + keyword_primary: what is a vex report +lang: en-US +ignoreChecks: null +--- + +# What is a VEX Report + +**VEX (Vulnerability Exploitability eXchange)** is a standard for communicating whether a known +vulnerability actually affects a specific product. This page explains the problem it solves, the design of +the standard, and the formats it is implemented in. + +## Where does VEX originate from + +Modern software depends on layers of third-party libraries, creating a real challenge around understanding +what components are in use and what risks they carry. The industry has developed solid infrastructure for +this: [Software Bills of Materials (SBOMs)](/explanations/explaining-sboms) inventory a product's +components, and [Software Composition Analysis (SCA)](/explanations/devsecops/software-composition-analysis) +tools cross-reference those inventories against vulnerability databases like the NVD or OSV, +surfacing any component version with a known CVE. + +But this pipeline has a fundamental limitation: it answers the question of what vulnerabilities exist *in +the components your product uses*, not whether those vulnerabilities actually affect *your product*. A CVE +describes a flaw in a library in the abstract. Whether that flaw poses real risk in a specific product +depends on factors the database cannot know: Is the vulnerable code included in the final build? Is it ever +reached during execution? Does the surrounding environment neutralize the threat? + +The consequence is a high rate of [false positives](/explanations/vulnerability-management/false-positive-detection). Findings that look alarming often turn out not to apply — +the vulnerable function is stripped from the production artifact, the affected code path is never enabled, +the exploit requires network access to a service that isn't exposed. At scale, security teams work through +hundreds of such findings per release cycle, most leading nowhere. The noise erodes trust in tooling and +makes it structurally difficult to act on the vulnerabilities that genuinely matter. + +VEX — short for Vulnerability Exploitability eXchange — emerged as a direct response to this gap. The +concept grew out of the NTIA's Software Component Transparency working group around 2020 and is now +stewarded by CISA as part of its broader SBOM effort. Where the existing ecosystem tells you *what +vulnerabilities are present* in a product's components, VEX provides a standardized way to communicate +*whether those vulnerabilities actually affect* a specific product — giving vendors and maintainers a format +to share the analysis they already perform internally, but have historically had no standard way to +distribute. + +## What is the VEX standard + +A VEX document is a machine-readable assertion about the relationship between a specific product and a +specific vulnerability. Where a CVE entry describes a flaw in a library, a VEX statement answers a +different question: does that flaw apply to *this* product, in *this* version, as shipped? The distinction +matters because the same vulnerability might affect one vendor's product while being completely irrelevant +to another's, even when both include the same underlying library. VEX gives vendors a standardized way to +make that context explicit, rather than leaving each consumer to re-derive the same analysis independently. + +The core of a VEX statement is a status, one of four values that together cover the complete space of how a +product can relate to a known vulnerability. A product might not be affected at all, might be affected and +require action, might already have a fix in place, or might still be under investigation. These are not +arbitrary categories; they map directly to the real states a security team works through when a new CVE +lands. Under investigation is where every triage starts. Not affected, affected, and fixed are where it +ends. The design reflects the actual workflow of vulnerability response, not an abstract taxonomy. + +The most carefully considered part of the standard is what happens when a vendor claims a product is not +affected. That status requires a justification, a machine-readable explanation of *why* the product isn't +vulnerable. The +standard defines five valid justification labels: the vulnerable component isn't present in the build, the +component is present but the vulnerable code isn't, the vulnerable code is present but never reached during +execution, the vulnerable code can't be controlled by an adversary, or an inline mitigation already +neutralizes the threat. This requirement exists because a not-affected claim is the hardest assertion to +verify from the outside. Without a justification, it is an ungrounded declaration. With one, it becomes +something that can +be audited, challenged, and trusted. + +VEX is a concept before it is a format. In practice it is implemented across three specifications: CSAF +VEX, CycloneDX VEX, and OpenVEX, each with its own encoding, tooling ecosystem, and design philosophy. +[CSAF](/explanations/compliance/csaf-vex-explained), developed by OASIS, is the most comprehensive and +suits organizations distributing formal security advisories. +[CycloneDX](/explanations/compliance/sbom-standards) embeds VEX data directly into its SBOM structure, +keeping component inventory and +exploitability information together. OpenVEX is the most minimal of the three, designed to be lightweight +and embeddable across formats. All three share the same fundamental data model: product, vulnerability, +status and justification, which means the underlying information translates across them, even if toolchain +interoperability remains imperfect in practice. + +## VEX in DevGuard + +DevGuard treats VEX as a built-in part of the vulnerability workflow: you record a status and justification +on a finding during triage, and DevGuard stores it as machine-readable VEX that can be exported or consumed +from upstream suppliers. For the hands-on steps, see the how-to guide on +[generating VEX documents](/how-to-guides/compliance/generate-vex-documents). + +## Related Documentation + +- [CSAF & VEX Standards](/explanations/compliance/csaf-vex-explained) — how the CSAF and CycloneDX VEX formats are structured, and how DevGuard publishes and consumes them +- [Vulnerability Lifecycle](/explanations/vulnerability-management/vulnerability-lifecycle) — how VEX states map onto the states a vulnerability moves through +- [False Positive Detection](/explanations/vulnerability-management/false-positive-detection) — the false-positive problem VEX is designed to address +- [External Vulnerability Sync](/explanations/vulnerability-management/external-vuln-sync) — importing and exporting vulnerability assessments