VEX policy — Vulnerability Exploitability eXchange¶
How TLSStress.Art communicates "yes there's a CVE in this dependency, but it doesn't apply to us because…" in a machine-readable format that scanners (Trivy, Grype, OSV) can consume.
What VEX is¶
VEX (Vulnerability Exploitability eXchange) is a NIST + CISA- backed format for asserting the exploitability status of a known CVE against a specific product. Two formats are widely supported:
- OpenVEX — JSON, minimal, human-readable
- CSAF VEX — XML profile of the broader CSAF advisory format
We publish in OpenVEX. Trivy + Grype consume it natively as of v0.45+.
When we file a VEX statement¶
A VEX statement is appropriate when one of these is true:
| Status | Meaning |
|---|---|
not_affected |
The vulnerable code path is unreachable in our usage (e.g. the function is dead code under our build flags) |
affected |
The CVE applies and we don't have a fix yet (this is an advisory we publish to be transparent) |
fixed |
The CVE applied to a prior release; we've patched in a release ≥ vX.Y.Z |
under_investigation |
We've been notified and are still triaging |
The first one — not_affected with a clear justification — is
the most common. Trivy / Grype scanning the release-shipped SBOM
will otherwise flag the CVE every time, drowning real signal in
noise.
Required not_affected justifications¶
OpenVEX defines five canonical justifications. We use these verbatim — never invent new ones, because scanners only honour the canonical strings:
| Justification | When to use |
|---|---|
component_not_present |
The dependency listed in the SBOM is not actually built into the shipping image (build-time-only dep) |
vulnerable_code_not_present |
The CVE applies to a function / module / feature flag that we don't compile in |
vulnerable_code_not_in_execute_path |
The vulnerable function is compiled in but no code path reaches it in our runtime |
vulnerable_code_cannot_be_controlled_by_adversary |
The input that triggers the CVE cannot be reached by an external attacker (e.g. only our test fixtures hit it) |
inline_mitigations_already_exist |
A WAF / NetworkPolicy / OPA gate / etc. prevents exploitation in our deployment |
Pick the most specific one that's actually true. Auditors read these.
Where VEX documents live¶
docs/security/vex/
├── README.md
├── 2026-XX-XX-CVE-YYYY-NNNN.openvex.json
└── ...
File naming: <advisory-date>-<CVE-id>.openvex.json. One file
per CVE per product version we're attesting against. The
docs/security/vex/ directory is populated lazily — when a real
CVE drops, we add a file.
Each VEX file is also attached to the affected release(s) as a release asset, so a scanner pointing at the GitHub release page picks it up automatically.
VEX statement template¶
{
"@context": "https://openvex.dev/ns/v0.2.0",
"@id": "https://github.com/nollagluiz/AI_forSE/security/vex/2026-05-12-CVE-2026-XXXX",
"author": "André Luiz Gallon <agallon@Cisco.com>",
"timestamp": "2026-05-12T15:00:00.000Z",
"version": 1,
"statements": [
{
"vulnerability": {
"@id": "https://nvd.nist.gov/vuln/detail/CVE-2026-XXXX",
"name": "CVE-2026-XXXX"
},
"products": [
{
"@id": "pkg:oci/web-agent-dashboard@sha256:XXXXXXXX?repository_url=ghcr.io/nollagluiz",
"identifiers": {
"purl": "pkg:oci/web-agent-dashboard@sha256:XXXXXXXX?repository_url=ghcr.io/nollagluiz"
}
}
],
"status": "not_affected",
"justification": "vulnerable_code_not_in_execute_path",
"impact_statement": "The vulnerable function `parseLegacyHeader` is exposed only when the HTTP/1.1 path is enabled. Our Caddy persona configuration forces `protocols h2 h3` (see ADR 0001 + docs/help-center/primers/), so the affected code is loaded but never reached by any agent or persona traffic."
}
]
}
Process for filing a VEX¶
When a Trivy / Grype scan against a release SBOM flags a CVE:
- Triage the CVE — read the upstream advisory + the vulnerable function's source. Determine which OpenVEX status applies.
- Pick the justification — must be one of the canonical five
for
not_affected, or skip ifaffected/fixed/under_investigation. - Write the impact_statement — plain English, one paragraph, citing the specific code path or config flag that makes the justification true. This is what an auditor reads first.
- Create the file at
docs/security/vex/<date>-<cve>.openvex.json. - Open a PR — uses the standard PR template; CODEOWNERS routes to the maintainer.
- Attach to releases — after merge, the PR author or
maintainer attaches the JSON file to every affected release on
GitHub's release-edit UI. The release-feed branch is also
updated with a
vex/directory pointer.
Verifying VEX is consumed correctly¶
After publishing a VEX file, re-scan with Trivy in VEX mode:
trivy sbom dashboard-sbom.spdx.json \
--vex docs/security/vex/2026-05-12-CVE-2026-XXXX.openvex.json
The CVE the VEX file references should now appear with status "not_affected" instead of "HIGH" / "CRITICAL". Confirm before shipping the VEX in production.
What VEX is NOT¶
- ❌ Not a fix — the vulnerable code is still there; the VEX just documents why it doesn't matter in our usage
- ❌ Not eternal — if our usage changes (e.g. we enable HTTP/1.1), the VEX becomes stale and must be revoked or amended
- ❌ Not a substitute for a security advisory — if the CVE
does apply, we file a GitHub Security Advisory + ship a
fix per SECURITY.md, then file a
status: fixedVEX for the release that contains the fix
Revoking or amending a VEX¶
If a VEX becomes incorrect (usage changed, new evidence emerged):
- Open a PR that either deletes the file or bumps its
versionfield and updatesstatus/justification. - Mark the release-page asset as superseded.
- Add an entry to
CHANGELOG.mdunder the release that bundles the correction.
Why this matters¶
For audit and valuation, the existence of a VEX policy says two things:
- We understand the difference between a CVE being present and being exploitable — many noncommercial projects don't
- We respond in a machine-readable way that downstream integrators can consume automatically
A scanner-clean release where every "present-but-not-exploitable" CVE has a paired VEX statement is materially less audit risk for a procurement reviewer than a scanner-noisy release with no VEX.
Related¶
- SECURITY.md — vulnerability-disclosure policy + SLA
- Backporting policy — security backport mechanics
- Release cadence — release-prep checklist (includes VEX-attach step)
- Verify a release runbook — how downstream users consume VEX
- OpenVEX spec — format reference
- Trivy VEX integration
Last verified against shipping code: v3.7.0 (2026-05-12).