Skip to content

Commit be1d314

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

74 files changed

Lines changed: 1837 additions & 1075 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: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,201 @@ The format is based on the regulated environment requirements:
99

1010
---
1111

12+
## [2026-04-22 20:30] - Presence-based auto-VEX (roadmap Phase 2)
13+
14+
**Author:** Erick Bourgeois
15+
16+
### Added
17+
- `src/auto_vex_presence.rs` (library module, ~180 LOC) + `src/auto_vex_presence_tests.rs`
18+
(20 unit tests, 100% positive/negative/exception coverage per project rule):
19+
- `compute_presence_vex` — pure function that diffs Grype findings against
20+
SBOMs and the already-triaged CVE set, emitting
21+
`not_affected + component_not_present` statements for CVEs whose purl
22+
is absent from every SBOM.
23+
- `build_document` — wraps statements in the OpenVEX envelope.
24+
- `load_triaged_from_vex_dir` — reads hand-authored `.vex/*.json` and
25+
collects the set of `vulnerability.name` values (permissive on
26+
missing dir, strict on malformed JSON).
27+
- Typed deserializers for Grype JSON and CycloneDX SBOM subsets —
28+
tolerant of unknown upstream fields.
29+
- Output sorted by CVE id for deterministic, diffable artifacts.
30+
- `src/bin/auto_vex_presence.rs` — thin clap-driven CLI wrapping the
31+
library. Reads `--grype-json`, one or more `--sbom` files, a
32+
`--vex-dir`, a `--product-purl`, `--id`, optional `--author` and
33+
`--timestamp`, writes an OpenVEX JSON to `--output` or stdout.
34+
- `Cargo.toml`: `[[bin]] auto-vex-presence` entry.
35+
- `Makefile`: `vex-auto-presence` target (defaults `GRYPE_JSON=grype.json`
36+
and `SBOM_FILES=target/release/*.cdx.json docker-sbom-*.json`; both
37+
overridable). Added to `.PHONY`.
38+
- `.github/workflows/build.yaml`: two new jobs gated on
39+
`github.event_name != 'pull_request'`:
40+
- `grype-triage` — runs Grype against each image variant without VEX
41+
suppression, uploads per-variant JSON (pinned GRYPE_VERSION 0.87.0
42+
matching the existing `grype` job).
43+
- `auto-vex-presence` — builds the new bin, downloads both triage
44+
scans + Docker SBOMs, runs the bin once per variant, merges the
45+
per-variant outputs via `vexctl merge`, uploads
46+
`vex-auto-presence` artifact.
47+
48+
### Changed
49+
- `.github/workflows/build.yaml`:
50+
- Docker SBOM generation (`Generate Docker SBOM for ${{ matrix.variant.name }}`):
51+
gate loosened from `if: github.event_name == 'release'` to
52+
`if: github.event_name != 'pull_request'` so push-to-main also
53+
produces SBOMs for auto-vex-presence to consume.
54+
- `build-vex` job: `needs` extended to include `auto-vex-presence`.
55+
Added a feature-flagged download step (`if: vars.AUTO_VEX_PRESENCE == '1'`)
56+
and conditional merge inclusion. When the repo variable is
57+
`AUTO_VEX_PRESENCE=1`, `vex.auto-presence.json` is merged alongside
58+
`.vex/*.json`; otherwise hand-authored only. Artifact is produced
59+
regardless — only the merge is gated.
60+
- `src/lib.rs`: re-export `pub mod auto_vex_presence`.
61+
- `docs/src/security/vex.md`: CI flow section renumbered to 6 steps;
62+
added step 2 for auto-presence. Rewrote "Why we did not auto-generate
63+
statements" section as "What we automate, and what stays human" — the
64+
policy now distinguishes `component_not_present` (mechanical,
65+
automatable) from every other triage decision (human-authored).
66+
- `.vex/README.md`: added "Automated statements (roadmap Phase 2)"
67+
section explaining the review-artifact vs flag-gated-merge split.
68+
69+
### Why
70+
Phase 2 of the automated VEX generation roadmap
71+
(`~/dev/roadmaps/5spot-automated-vex-generation.md`). Most of the
72+
hand-authored statements under `.vex/` exist because Grype flags
73+
CVEs on libc / glibc / zlib code paths that the Rust binary never
74+
reaches — i.e. the vulnerable component is present in the image
75+
filesystem but never invoked. A subset of those (plus most
76+
base-image CVEs going forward) can be suppressed mechanically by
77+
observing that the flagged package's purl is not in the SBOM at
78+
all: the SBOM is authoritative for what's in the product, so
79+
"component_not_present" is the one OpenVEX justification with a
80+
purely verifiable definition. Automating that specific case shrinks
81+
the `.vex/` maintenance queue without compromising the trust model
82+
for any other justification.
83+
84+
The implementation is strictly SBOM-driven — no reachability analysis,
85+
no source-code inspection. Reachability is Phase 3.
86+
87+
### Rollout / feature-flag
88+
Per the roadmap rollout plan, the auto-presence artifact is
89+
produced and uploaded on every push + release, but it is **only
90+
merged into the signed VEX document when `vars.AUTO_VEX_PRESENCE=1`
91+
is set** at the repository or organization level. This gives
92+
maintainers at least one release of parallel-run validation: the
93+
artifact is visible for review, but consumers see only the
94+
hand-authored VEX until the flag is flipped. This matches the
95+
roadmap's explicit guidance ("Run in parallel with hand-authored VEX
96+
for one release; diff the auto-set against .vex/ and confirm no
97+
surprises before flipping it on").
98+
99+
### Verification
100+
- `cargo fmt --all -- --check`: clean.
101+
- `cargo clippy --all-targets --all-features -- -D warnings`: clean.
102+
- `cargo test --lib`: 349 tests pass (329 pre-existing + 20 new for
103+
`auto_vex_presence`).
104+
- Smoke test: ran the bin against a synthetic Grype JSON (3 matches),
105+
synthetic SBOM (covering 1 of the 3 purls), and the current `.vex/`
106+
directory (covering 1 of the 3 CVEs). The bin emitted exactly the
107+
one statement for the remaining CVE whose purl was absent, and
108+
`vexctl merge` of the auto-presence output + `.vex/*.json`
109+
produced a 16-statement document with the auto statement correctly
110+
included.
111+
112+
### Impact
113+
- [ ] Breaking change
114+
- [x] Requires cluster rollout (no — CI only)
115+
- [ ] Config change only
116+
- [x] Documentation only
117+
118+
*(Rollout note: when maintainers want to flip `AUTO_VEX_PRESENCE=1`,
119+
that's a repo-variable change in GitHub settings, no code deploy.)*
120+
121+
---
122+
123+
## [2026-04-22 19:45] - Replace Python VEX tooling with vexctl (roadmap Phase 1)
124+
125+
**Author:** Erick Bourgeois
126+
127+
### Removed
128+
- `tools/` — entire directory deleted. Removed `assemble_openvex.py`,
129+
`validate_vex.py`, `validate-vex.sh`, `tests/assemble-openvex-tests.sh`,
130+
`tests/validate-vex-tests.sh`, and all 18 fixture directories under
131+
`tests/fixtures/`. ~400 LOC of bespoke Python + shell tests replaced
132+
by upstream `vexctl` invocations.
133+
134+
### Changed
135+
- `.vex/*.toml``.vex/*.json` (15 files): migrated every statement
136+
from the bespoke TOML dialect to the native OpenVEX v0.2.0 JSON
137+
shape. Each file is now a single-statement OpenVEX document that
138+
`vexctl merge` can consume directly; no translation layer.
139+
- `Makefile`: added `VEXCTL_VERSION ?= 0.4.1`, `vexctl-install` target
140+
(brew on macOS, pinned GitHub release tarball with checksum
141+
verification on Linux; errors on other OSes), `vex-validate`
142+
(parses every `.vex/*.json` via `vexctl merge` — successful parse
143+
is the validation), and `vex-assemble` (prints the merged document
144+
for local preview). `.PHONY` list updated.
145+
- `.github/workflows/build.yaml`:
146+
- `validate-vex` job: dropped Python setup + custom validator calls;
147+
now runs `make vexctl-install` then `make vex-validate`.
148+
- `build-vex` job: dropped Python setup + `assemble_openvex.py`;
149+
now runs `vexctl merge --id <per-event-id> --author <actor>
150+
.vex/*.json` directly. Per-event `@id` logic (release / push /
151+
PR) preserved verbatim. Cosign attestation, artifact upload,
152+
and `attest-build-provenance` steps unchanged.
153+
- PR `paths:` filter: removed `tools/**` (directory no longer
154+
exists); `.vex/**` retained.
155+
- `.vex/README.md`: rewritten to document native OpenVEX JSON
156+
authoring and `make vex-validate` / `make vex-assemble` instead of
157+
the old TOML schema and shell validator.
158+
- `docs/src/security/vex.md`: CI flow section updated — step 1
159+
("validate") and step 2 ("assemble") now describe `vexctl merge`
160+
behaviour; dropped the obsolete "cross-check with `vexctl validate`"
161+
step (vexctl has no `validate` subcommand). Maintainer-authoring
162+
section rewritten with the JSON shape in place of the TOML shape.
163+
- `.claude/SKILL.md`: release checklist item for VEX updated from the
164+
old three-script check to `make vex-validate`.
165+
166+
### Why
167+
Phase 1 of the automated VEX generation roadmap
168+
(`~/dev/roadmaps/5spot-automated-vex-generation.md`). The custom
169+
Python toolchain duplicated functionality that `vexctl` — the official
170+
OpenVEX CLI — provides upstream. Maintaining a bespoke TOML dialect,
171+
its validator, its assembler, and two fixture-based test suites
172+
added ~400 LOC of surface area with no offsetting benefit once we
173+
accept OpenVEX JSON as the authoring format. Replacing the whole
174+
stack with `vexctl merge` (which doubles as the validator via
175+
successful parse) removes the Python runtime from CI entirely,
176+
eliminates a class of drift between our validator and the upstream
177+
spec, and prepares the pipeline for Phases 2–3 (automated VEX
178+
generation from SBOM and call-graph reachability).
179+
180+
The TOML-over-JSON ergonomics hit is real but small: 15 files in
181+
`.vex/`, each ~15 lines of JSON, with no inline comments. In
182+
exchange, future maintainers work with the same format upstream
183+
tooling speaks and any OpenVEX-aware tool can consume the source
184+
directory directly.
185+
186+
### Verification
187+
- `make vex-validate` parses all 15 `.vex/*.json` successfully.
188+
- `vexctl merge --id <x> --author <y> .vex/*.json` produces a
189+
15-statement document structurally identical to what the Python
190+
assembler would have produced (confirmed via key-by-key diff of
191+
`@context`, `@id`, `author`, `version`, and every statement's
192+
`products`, `status`, `justification`, `impact_statement`,
193+
`action_statement`, `timestamp`).
194+
195+
### Impact
196+
- [ ] Breaking change
197+
- [ ] Requires cluster rollout
198+
- [ ] Config change only
199+
- [x] Documentation only
200+
201+
*(Not really "documentation only" — code deletion is substantive —
202+
but no runtime, CRD, or API-surface change. Closest pre-existing
203+
box.)*
204+
205+
---
206+
12207
## [2026-04-21 15:00] - Model emergency-reclaim in CALM — ADD retrofit
13208

14209
**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.

0 commit comments

Comments
 (0)