Skip to content

Commit 5181958

Browse files
committed
Move to automating VEX
Signed-off-by: Erick Bourgeois <erick@jeb.ca>
1 parent 87c977b commit 5181958

69 files changed

Lines changed: 608 additions & 1068 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/CHANGELOG.md

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,90 @@ The format is based on the regulated environment requirements:
99

1010
---
1111

12+
## [2026-04-22 19:45] - Replace Python VEX tooling with vexctl (roadmap Phase 1)
13+
14+
**Author:** Erick Bourgeois
15+
16+
### Removed
17+
- `tools/` — entire directory deleted. Removed `assemble_openvex.py`,
18+
`validate_vex.py`, `validate-vex.sh`, `tests/assemble-openvex-tests.sh`,
19+
`tests/validate-vex-tests.sh`, and all 18 fixture directories under
20+
`tests/fixtures/`. ~400 LOC of bespoke Python + shell tests replaced
21+
by upstream `vexctl` invocations.
22+
23+
### Changed
24+
- `.vex/*.toml``.vex/*.json` (15 files): migrated every statement
25+
from the bespoke TOML dialect to the native OpenVEX v0.2.0 JSON
26+
shape. Each file is now a single-statement OpenVEX document that
27+
`vexctl merge` can consume directly; no translation layer.
28+
- `Makefile`: added `VEXCTL_VERSION ?= 0.4.1`, `vexctl-install` target
29+
(brew on macOS, pinned GitHub release tarball with checksum
30+
verification on Linux; errors on other OSes), `vex-validate`
31+
(parses every `.vex/*.json` via `vexctl merge` — successful parse
32+
is the validation), and `vex-assemble` (prints the merged document
33+
for local preview). `.PHONY` list updated.
34+
- `.github/workflows/build.yaml`:
35+
- `validate-vex` job: dropped Python setup + custom validator calls;
36+
now runs `make vexctl-install` then `make vex-validate`.
37+
- `build-vex` job: dropped Python setup + `assemble_openvex.py`;
38+
now runs `vexctl merge --id <per-event-id> --author <actor>
39+
.vex/*.json` directly. Per-event `@id` logic (release / push /
40+
PR) preserved verbatim. Cosign attestation, artifact upload,
41+
and `attest-build-provenance` steps unchanged.
42+
- PR `paths:` filter: removed `tools/**` (directory no longer
43+
exists); `.vex/**` retained.
44+
- `.vex/README.md`: rewritten to document native OpenVEX JSON
45+
authoring and `make vex-validate` / `make vex-assemble` instead of
46+
the old TOML schema and shell validator.
47+
- `docs/src/security/vex.md`: CI flow section updated — step 1
48+
("validate") and step 2 ("assemble") now describe `vexctl merge`
49+
behaviour; dropped the obsolete "cross-check with `vexctl validate`"
50+
step (vexctl has no `validate` subcommand). Maintainer-authoring
51+
section rewritten with the JSON shape in place of the TOML shape.
52+
- `.claude/SKILL.md`: release checklist item for VEX updated from the
53+
old three-script check to `make vex-validate`.
54+
55+
### Why
56+
Phase 1 of the automated VEX generation roadmap
57+
(`~/dev/roadmaps/5spot-automated-vex-generation.md`). The custom
58+
Python toolchain duplicated functionality that `vexctl` — the official
59+
OpenVEX CLI — provides upstream. Maintaining a bespoke TOML dialect,
60+
its validator, its assembler, and two fixture-based test suites
61+
added ~400 LOC of surface area with no offsetting benefit once we
62+
accept OpenVEX JSON as the authoring format. Replacing the whole
63+
stack with `vexctl merge` (which doubles as the validator via
64+
successful parse) removes the Python runtime from CI entirely,
65+
eliminates a class of drift between our validator and the upstream
66+
spec, and prepares the pipeline for Phases 2–3 (automated VEX
67+
generation from SBOM and call-graph reachability).
68+
69+
The TOML-over-JSON ergonomics hit is real but small: 15 files in
70+
`.vex/`, each ~15 lines of JSON, with no inline comments. In
71+
exchange, future maintainers work with the same format upstream
72+
tooling speaks and any OpenVEX-aware tool can consume the source
73+
directory directly.
74+
75+
### Verification
76+
- `make vex-validate` parses all 15 `.vex/*.json` successfully.
77+
- `vexctl merge --id <x> --author <y> .vex/*.json` produces a
78+
15-statement document structurally identical to what the Python
79+
assembler would have produced (confirmed via key-by-key diff of
80+
`@context`, `@id`, `author`, `version`, and every statement's
81+
`products`, `status`, `justification`, `impact_statement`,
82+
`action_statement`, `timestamp`).
83+
84+
### Impact
85+
- [ ] Breaking change
86+
- [ ] Requires cluster rollout
87+
- [ ] Config change only
88+
- [x] Documentation only
89+
90+
*(Not really "documentation only" — code deletion is substantive —
91+
but no runtime, CRD, or API-surface change. Closest pre-existing
92+
box.)*
93+
94+
---
95+
1296
## [2026-04-21 15:00] - Model emergency-reclaim in CALM — ADD retrofit
1397

1498
**Author:** Erick Bourgeois

.claude/SKILL.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -326,8 +326,7 @@ done
326326

327327
### If preparing a release:
328328
- [ ] Every open Trivy finding has a corresponding statement in `.vex/` (triaged into one of: `not_affected`, `affected`, `fixed`, `under_investigation`). No silent "unknown" CVEs leave the door.
329-
- [ ] `./tools/validate-vex.sh` exits 0.
330-
- [ ] `./tools/tests/validate-vex-tests.sh` and `./tools/tests/assemble-openvex-tests.sh` pass.
329+
- [ ] `make vex-validate` exits 0 (every `.vex/*.json` parses cleanly via `vexctl merge`).
331330
- [ ] `.vex/` changes referenced in the release notes / changelog.
332331

333332
**Verification:** Every checked box above passes. A task is NOT complete until the full checklist is green.

.github/workflows/build.yaml

Lines changed: 24 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ on:
1515
- '.github/workflows/build.yaml'
1616
- '.github/actions/**'
1717
- '.vex/**'
18-
- 'tools/**'
1918
push:
2019
branches:
2120
- main
@@ -139,10 +138,11 @@ jobs:
139138
run: cargo clippy --all-targets --all-features -- -D warnings
140139

141140
# ─────────────────────────────────────────────────────────────────────────────
142-
# VEX schema gate — PR only.
143-
# Enforces schema, enum, and CVE-uniqueness on every file under .vex/ so that
144-
# malformed VEX statements can never merge to main. The validator itself has
145-
# its own fixture-based unit tests which also run here.
141+
# VEX parse gate — PR only.
142+
# Every .vex/<id>.json is a single-statement OpenVEX document (the native
143+
# OpenVEX shape). vexctl merge parses each one as input; any malformed file
144+
# fails the merge, giving us free schema + enum + structural validation
145+
# from upstream tooling. No custom validator to maintain.
146146
# ─────────────────────────────────────────────────────────────────────────────
147147
validate-vex:
148148
name: 🧾 Validate VEX Statements
@@ -153,16 +153,11 @@ jobs:
153153
- name: Checkout code
154154
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
155155

156-
- name: Set up Python
157-
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
158-
with:
159-
python-version: '3.12'
160-
161-
- name: Run validator unit tests
162-
run: ./tools/tests/validate-vex-tests.sh
156+
- name: Install vexctl
157+
run: make vexctl-install
163158

164-
- name: Validate .vex/ directory
165-
run: ./tools/validate-vex.sh
159+
- name: Parse every .vex/*.json via vexctl merge
160+
run: make vex-validate
166161

167162
# ─────────────────────────────────────────────────────────────────────────────
168163
# Extract version — all events; downstream build jobs depend on this.
@@ -492,15 +487,18 @@ jobs:
492487
# ─────────────────────────────────────────────────────────────────────────────
493488
# VEX document — push to main + release.
494489
#
495-
# Reads hand-authored per-CVE triage from .vex/*.toml, assembles a single
496-
# OpenVEX document, and (on push/release only) Cosign-attests it to both
497-
# image digests. The `openvex` artifact is always uploaded so the `grype`
498-
# container scan job can consume it via `--vex` and suppress already-
499-
# triaged findings from GitHub Code Scanning. On release, the GitHub
500-
# build-provenance bundle is also emitted and attached to the release.
490+
# Reads hand-authored per-CVE triage from .vex/*.json (each file is a
491+
# single-statement native OpenVEX document), merges them into a single
492+
# document via `vexctl merge`, and (on push/release only) Cosign-attests
493+
# it to both image digests. The `openvex` artifact is always uploaded so
494+
# the `grype` container scan job can consume it via `--vex` and suppress
495+
# already-triaged findings from GitHub Code Scanning. On release, the
496+
# GitHub build-provenance bundle is also emitted and attached to the
497+
# release.
501498
#
502-
# The validator runs first so malformed VEX can never leak through, even
503-
# if a bad merge slipped past the PR gate.
499+
# vexctl merge fails on any malformed input, so there's no separate
500+
# validator step — a bad merge slipping past the PR gate would also fail
501+
# the assembly here.
504502
# ─────────────────────────────────────────────────────────────────────────────
505503
build-vex:
506504
name: 🧾 Assemble OpenVEX
@@ -516,13 +514,8 @@ jobs:
516514
- name: Checkout code
517515
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
518516

519-
- name: Set up Python
520-
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
521-
with:
522-
python-version: '3.12'
523-
524-
- name: Validate .vex/ (defensive re-run before assembly)
525-
run: ./tools/validate-vex.sh
517+
- name: Install vexctl
518+
run: make vexctl-install
526519

527520
# Canonical @id varies by event so downstream consumers can link back
528521
# to the exact commit or release tag that produced the document.
@@ -541,11 +534,10 @@ jobs:
541534
vex_id="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}/vex"
542535
;;
543536
esac
544-
python3 tools/assemble_openvex.py \
545-
--vex-dir .vex \
537+
vexctl merge \
546538
--id "$vex_id" \
547539
--author "${{ github.event.release.author.login || github.actor }}" \
548-
--output vex.openvex.json
540+
.vex/*.json > vex.openvex.json
549541
echo "── vex.openvex.json ─────────────────────────────────────────"
550542
cat vex.openvex.json
551543

.vex/CVE-2010-4756.json

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"@context": "https://openvex.dev/ns/v0.2.0",
3+
"@id": "https://github.com/finos/5-spot/.vex/CVE-2010-4756",
4+
"author": "erick.bourgeois@gmail.com",
5+
"statements": [
6+
{
7+
"impact_statement": "5-Spot performs no file-system glob expansion; the vulnerable glibc glob() implementation is never called from the controller.",
8+
"justification": "vulnerable_code_not_in_execute_path",
9+
"products": [
10+
{
11+
"@id": "pkg:oci/5-spot"
12+
}
13+
],
14+
"status": "not_affected",
15+
"timestamp": "2026-04-19T00:00:00Z",
16+
"vulnerability": {
17+
"name": "CVE-2010-4756"
18+
}
19+
}
20+
],
21+
"timestamp": "2026-04-19T00:00:00Z",
22+
"version": 1
23+
}

.vex/CVE-2010-4756.toml

Lines changed: 0 additions & 12 deletions
This file was deleted.

.vex/CVE-2018-20796.json

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"@context": "https://openvex.dev/ns/v0.2.0",
3+
"@id": "https://github.com/finos/5-spot/.vex/CVE-2018-20796",
4+
"author": "erick.bourgeois@gmail.com",
5+
"statements": [
6+
{
7+
"impact_statement": "5-Spot uses the Rust regex crate, a separate implementation from glibc's posix/regexec.c; the vulnerable C function is never invoked.",
8+
"justification": "vulnerable_code_not_in_execute_path",
9+
"products": [
10+
{
11+
"@id": "pkg:oci/5-spot"
12+
}
13+
],
14+
"status": "not_affected",
15+
"timestamp": "2026-04-19T00:00:00Z",
16+
"vulnerability": {
17+
"name": "CVE-2018-20796"
18+
}
19+
}
20+
],
21+
"timestamp": "2026-04-19T00:00:00Z",
22+
"version": 1
23+
}

.vex/CVE-2018-20796.toml

Lines changed: 0 additions & 12 deletions
This file was deleted.

.vex/CVE-2019-1010022.json

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"@context": "https://openvex.dev/ns/v0.2.0",
3+
"@id": "https://github.com/finos/5-spot/.vex/CVE-2019-1010022",
4+
"author": "erick.bourgeois@gmail.com",
5+
"statements": [
6+
{
7+
"impact_statement": "The controller does not execute adversary-supplied native code; the pre-conditions required to reach the glibc stack-guard bypass do not exist in this workload.",
8+
"justification": "vulnerable_code_cannot_be_controlled_by_adversary",
9+
"products": [
10+
{
11+
"@id": "pkg:oci/5-spot"
12+
}
13+
],
14+
"status": "not_affected",
15+
"timestamp": "2026-04-19T00:00:00Z",
16+
"vulnerability": {
17+
"name": "CVE-2019-1010022"
18+
}
19+
}
20+
],
21+
"timestamp": "2026-04-19T00:00:00Z",
22+
"version": 1
23+
}

.vex/CVE-2019-1010022.toml

Lines changed: 0 additions & 13 deletions
This file was deleted.

.vex/CVE-2019-1010023.json

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"@context": "https://openvex.dev/ns/v0.2.0",
3+
"@id": "https://github.com/finos/5-spot/.vex/CVE-2019-1010023",
4+
"author": "erick.bourgeois@gmail.com",
5+
"statements": [
6+
{
7+
"impact_statement": "The Distroless image ships no ldd or shell; the vulnerable workflow (running ldd on an adversary-supplied ELF) cannot occur at runtime.",
8+
"justification": "vulnerable_code_not_in_execute_path",
9+
"products": [
10+
{
11+
"@id": "pkg:oci/5-spot"
12+
}
13+
],
14+
"status": "not_affected",
15+
"timestamp": "2026-04-19T00:00:00Z",
16+
"vulnerability": {
17+
"name": "CVE-2019-1010023"
18+
}
19+
}
20+
],
21+
"timestamp": "2026-04-19T00:00:00Z",
22+
"version": 1
23+
}

0 commit comments

Comments
 (0)