This directory is the human-authored source of truth for 5-Spot's
VEX (Vulnerability Exploitability eXchange)
statements. Each .vex/<id>.json is a single-statement
OpenVEX
document in the native format. CI merges every file at release time via
vexctl into a single signed
document that is:
- attached to the GitHub Release as an asset,
- recorded in
checksums.sha256, - Cosign-attested against every published image digest (Chainguard + Distroless), and
- GitHub-attested via
actions/attest-build-provenance.
Downstream scanners (Grype, Trivy, Harbor) consume the OpenVEX document and suppress findings we have already triaged as not applicable.
On every push + release, CI produces two automated VEX artifacts that are merged unconditionally into the signed release VEX alongside the hand-authored statements in this directory:
vex-auto-presence(roadmap Phase 2,src/auto_vex_presence.rs). Emitsnot_affected + component_not_presentstatements for every Grype finding whose affected package URL is absent from every image SBOM and is not already triaged here. The SBOM digest is the evidence.vex-auto-reachability(roadmap Phase 3,src/auto_vex_reachability.rs). Emitsnot_affected + vulnerable_code_not_in_execute_pathstatements for every Grype finding whose CVE id is present in the curated.affected-functions.jsonmapping (see below) and whose listed library functions are all absent from the release binary's dynamic symbol-import table. The accompanyingvex-auto-reachability-evidenceartifact is the rawnm -D --undefined-onlyoutput the analyzer used.
Verification of the merged document — including both auto-generated sets — is performed downstream by the Security team, which re-evaluates each claim against the attached evidence (SBOMs, symbol-imports, Cosign attestations, SLSA provenance) and counter-signs the document if they agree. There is no parallel-run gate on our side: our job is to emit as aggressively as the evidence supports; their job is to verify.
A curated CVE → list-of-public-API-function-names mapping consumed
by the Phase 3 reachability check. The file is dot-prefixed so
default shell globs (e.g., vexctl merge .vex/*.json) skip it.
Underscore-prefixed keys (_comment, _meta) are sidecar metadata
and are ignored by the parser.
Example entry:
"CVE-2010-4756": ["glob", "fnmatch"]The list names the public C library entry points the affected
code path is reached through. Internal helpers (e.g. glibc's
check_dst_limits_calc_pos_1 inside regexec) are intentionally
not listed — the auto-reachability check inspects the binary's
dynamic-symbol-import table, which only sees public APIs. CVEs
whose affected behaviour is not function-bounded (adversary-model
preconditions, ASLR bypasses, missing-shell-at-runtime, etc.) are
deliberately omitted from the map and stay hand-authored in their
respective .vex/<id>.json files. The bin treats "no entry" as "do
not auto-derive", not as "reachable".
When a scanner (Grype in CI, or a downstream consumer) flags a CVE on a 5-Spot release artifact, open a PR adding one file per CVE in this directory. Merging is gated by:
make vex-validate— parses every.vex/*.jsonviavexctl merge; any malformed file fails the merge and blocks the PR.- Human review of the impact statement.
No automated "everything is not_affected" statements are written. Every
statement is explicitly authored and reviewed.
One JSON file per advisory, named <identifier>.json. Accepted
identifier shapes for the vulnerability.name field:
CVE-YYYY-NNNN+— MITRE CVE (the common case).GHSA-xxxx-xxxx-xxxx— GitHub Security Advisory (used when the advisory has no assigned CVE yet, e.g.GHSA-cq8v-f236-94qc).RUSTSEC-YYYY-NNNN— RustSec advisory DB.
Each file is a single-statement OpenVEX v0.2.0 document:
{
"@context": "https://openvex.dev/ns/v0.2.0",
"@id": "https://github.com/finos/5-spot/.vex/CVE-2025-12345",
"author": "erick.bourgeois@gmail.com",
"timestamp": "2026-04-19T00:00:00Z",
"version": 1,
"statements": [
{
"vulnerability": {"name": "CVE-2025-12345"},
"products": [{"@id": "pkg:oci/5-spot"}],
"status": "not_affected",
"justification": "vulnerable_code_not_in_execute_path",
"impact_statement": "5-Spot does not parse untrusted XML; the affected libxml2 code path is never invoked.",
"timestamp": "2026-04-19T00:00:00Z"
}
]
}The document-level @id, author, and timestamp are replaced by
CI at release time; the statement-level fields (inside statements[])
are what ship in the merged release document.
| Field | Required | Notes |
|---|---|---|
vulnerability.name |
yes | Canonical identifier: CVE-YYYY-NNNN+, GHSA-xxxx-xxxx-xxxx, or RUSTSEC-YYYY-NNNN. |
status |
yes | One of: not_affected, affected, fixed, under_investigation. |
justification |
required iff status = "not_affected" |
OpenVEX enum, see below. |
impact_statement |
recommended for not_affected |
Free-form explanation of why the CVE is non-exploitable in 5-Spot. |
action_statement |
required iff status = "affected" or "under_investigation" |
What a consumer should do until a fix is available (e.g. upgrade path, mitigation). |
products |
yes, non-empty | List of product identifiers. Use package URLs (pkg:oci/...) or image references. |
timestamp |
yes | RFC-3339 UTC timestamp. |
Per the OpenVEX spec:
component_not_presentvulnerable_code_not_presentvulnerable_code_not_in_execute_pathvulnerable_code_cannot_be_controlled_by_adversaryinline_mitigations_already_exist
make vex-validateThis installs vexctl if missing and runs vexctl merge over every
.vex/*.json. Any malformed file fails the merge — successful
parse = valid structure, valid enum values, and no duplicate
statements for the same (vulnerability, product) pair.
make vex-assemblePrints the merged OpenVEX document to stdout. Useful for previewing the release-time artifact locally.