Skip to content

Commit c008018

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

74 files changed

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

1010
---
1111

12+
## [2026-04-22 21:15] - Drop AUTO_VEX_PRESENCE gate; Security team is the verifier
13+
14+
**Author:** Erick Bourgeois
15+
16+
### Changed
17+
- `.github/workflows/build.yaml`:
18+
- `build-vex` job: removed the `vars.AUTO_VEX_PRESENCE == '1'`
19+
conditional on the auto-presence download step. The artifact is
20+
now downloaded unconditionally, and the merge command includes
21+
`auto/vex.auto-presence.json` on every build. Added an explicit
22+
`test -f auto/vex.auto-presence.json || exit 1` guard so a
23+
missing artifact fails the job (rather than silently producing
24+
hand-authored-only VEX).
25+
- Comment headers on both `auto-vex-presence` and `build-vex`
26+
rewritten to describe the new trust model: CI emits aggressively,
27+
Security team verifies downstream and counter-signs.
28+
- `.vex/README.md`: removed the "parallel-run gate / review-only"
29+
language. Auto-presence statements now merge into the signed VEX on
30+
every build.
31+
- `docs/src/security/vex.md`:
32+
- CI flow step 3: removed the `AUTO_VEX_PRESENCE=1` gate clause.
33+
- Automation-split section: the "Automated" bullet no longer
34+
mentions a feature flag.
35+
- New top-level "Trust model" section describing the two-signature
36+
workflow: CI-side Cosign attestation (keyless OIDC via GitHub)
37+
first, Security-team Cosign attestation (their own OIDC identity)
38+
second. Each sits in the Sigstore transparency log; downstream
39+
consumers can require both via twin
40+
`--certificate-identity-regexp` invocations.
41+
- `~/dev/roadmaps/5spot-automated-vex-generation.md`:
42+
- Top-of-file status line updated: Phase 1 ✅, Phase 2 ✅ (always-on),
43+
Phase 3 ⏳ scoped.
44+
- New "Trust model (load-bearing)" section at the top — replaces
45+
the implicit assumption in the earlier draft that our side would
46+
be the verification gate.
47+
- Phase 2 "Status" line updated to reflect the gate removal.
48+
- Phase 3 tradeoff section simplified: "two independent call-graph
49+
analyses must agree" and "multi-release parallel-run" removed;
50+
the Security team's re-verification is the second check.
51+
- Rollout section rewritten: no per-phase feature flags; each
52+
phase activates on merge.
53+
54+
### Why
55+
Policy flip requested by the user. The original Phase 2 rollout plan
56+
had an `AUTO_VEX_PRESENCE` repo variable that gated whether
57+
auto-generated statements flowed into the signed VEX document; the
58+
intent was a parallel-run validation window where maintainers could
59+
diff the auto-set against `.vex/` before authorizing it. The revised
60+
trust model moves that validation downstream to the Security team,
61+
which re-derives each machine-authored claim from the signed evidence
62+
(SBOM, call-graph attestations, SLSA provenance) and counter-signs if
63+
they agree. That makes our side's gate redundant — if we get it
64+
wrong, the VEX ships with only one attestation instead of two, which
65+
is a discoverable condition for any downstream consumer running a
66+
two-signature verify gate.
67+
68+
The trust model extends to the not-yet-implemented Phase 3
69+
(reachability-based auto-VEX): it also ships unconditionally when
70+
built, with call-graph attestations providing the evidence for
71+
Security to verify.
72+
73+
### Verification
74+
- `python3 -c "import yaml; yaml.safe_load(open('.github/workflows/build.yaml'))"`
75+
→ valid YAML.
76+
- `grep "AUTO_VEX_PRESENCE" .github/workflows/build.yaml` → no
77+
residual references.
78+
- No Rust code touched; `cargo` checks unchanged from the previous
79+
entry.
80+
81+
### Impact
82+
- [ ] Breaking change
83+
- [ ] Requires cluster rollout
84+
- [x] Config change only — specifically, removes a config knob that
85+
existed for ~1 hour and was never documented as user-facing.
86+
- [x] Documentation only
87+
88+
---
89+
90+
## [2026-04-22 20:30] - Presence-based auto-VEX (roadmap Phase 2)
91+
92+
**Author:** Erick Bourgeois
93+
94+
### Added
95+
- `src/auto_vex_presence.rs` (library module, ~180 LOC) + `src/auto_vex_presence_tests.rs`
96+
(20 unit tests, 100% positive/negative/exception coverage per project rule):
97+
- `compute_presence_vex` — pure function that diffs Grype findings against
98+
SBOMs and the already-triaged CVE set, emitting
99+
`not_affected + component_not_present` statements for CVEs whose purl
100+
is absent from every SBOM.
101+
- `build_document` — wraps statements in the OpenVEX envelope.
102+
- `load_triaged_from_vex_dir` — reads hand-authored `.vex/*.json` and
103+
collects the set of `vulnerability.name` values (permissive on
104+
missing dir, strict on malformed JSON).
105+
- Typed deserializers for Grype JSON and CycloneDX SBOM subsets —
106+
tolerant of unknown upstream fields.
107+
- Output sorted by CVE id for deterministic, diffable artifacts.
108+
- `src/bin/auto_vex_presence.rs` — thin clap-driven CLI wrapping the
109+
library. Reads `--grype-json`, one or more `--sbom` files, a
110+
`--vex-dir`, a `--product-purl`, `--id`, optional `--author` and
111+
`--timestamp`, writes an OpenVEX JSON to `--output` or stdout.
112+
- `Cargo.toml`: `[[bin]] auto-vex-presence` entry.
113+
- `Makefile`: `vex-auto-presence` target (defaults `GRYPE_JSON=grype.json`
114+
and `SBOM_FILES=target/release/*.cdx.json docker-sbom-*.json`; both
115+
overridable). Added to `.PHONY`.
116+
- `.github/workflows/build.yaml`: two new jobs gated on
117+
`github.event_name != 'pull_request'`:
118+
- `grype-triage` — runs Grype against each image variant without VEX
119+
suppression, uploads per-variant JSON (pinned GRYPE_VERSION 0.87.0
120+
matching the existing `grype` job).
121+
- `auto-vex-presence` — builds the new bin, downloads both triage
122+
scans + Docker SBOMs, runs the bin once per variant, merges the
123+
per-variant outputs via `vexctl merge`, uploads
124+
`vex-auto-presence` artifact.
125+
126+
### Changed
127+
- `.github/workflows/build.yaml`:
128+
- Docker SBOM generation (`Generate Docker SBOM for ${{ matrix.variant.name }}`):
129+
gate loosened from `if: github.event_name == 'release'` to
130+
`if: github.event_name != 'pull_request'` so push-to-main also
131+
produces SBOMs for auto-vex-presence to consume.
132+
- `build-vex` job: `needs` extended to include `auto-vex-presence`.
133+
Added a feature-flagged download step (`if: vars.AUTO_VEX_PRESENCE == '1'`)
134+
and conditional merge inclusion. When the repo variable is
135+
`AUTO_VEX_PRESENCE=1`, `vex.auto-presence.json` is merged alongside
136+
`.vex/*.json`; otherwise hand-authored only. Artifact is produced
137+
regardless — only the merge is gated.
138+
- `src/lib.rs`: re-export `pub mod auto_vex_presence`.
139+
- `docs/src/security/vex.md`: CI flow section renumbered to 6 steps;
140+
added step 2 for auto-presence. Rewrote "Why we did not auto-generate
141+
statements" section as "What we automate, and what stays human" — the
142+
policy now distinguishes `component_not_present` (mechanical,
143+
automatable) from every other triage decision (human-authored).
144+
- `.vex/README.md`: added "Automated statements (roadmap Phase 2)"
145+
section explaining the review-artifact vs flag-gated-merge split.
146+
147+
### Why
148+
Phase 2 of the automated VEX generation roadmap
149+
(`~/dev/roadmaps/5spot-automated-vex-generation.md`). Most of the
150+
hand-authored statements under `.vex/` exist because Grype flags
151+
CVEs on libc / glibc / zlib code paths that the Rust binary never
152+
reaches — i.e. the vulnerable component is present in the image
153+
filesystem but never invoked. A subset of those (plus most
154+
base-image CVEs going forward) can be suppressed mechanically by
155+
observing that the flagged package's purl is not in the SBOM at
156+
all: the SBOM is authoritative for what's in the product, so
157+
"component_not_present" is the one OpenVEX justification with a
158+
purely verifiable definition. Automating that specific case shrinks
159+
the `.vex/` maintenance queue without compromising the trust model
160+
for any other justification.
161+
162+
The implementation is strictly SBOM-driven — no reachability analysis,
163+
no source-code inspection. Reachability is Phase 3.
164+
165+
### Rollout / feature-flag
166+
Per the roadmap rollout plan, the auto-presence artifact is
167+
produced and uploaded on every push + release, but it is **only
168+
merged into the signed VEX document when `vars.AUTO_VEX_PRESENCE=1`
169+
is set** at the repository or organization level. This gives
170+
maintainers at least one release of parallel-run validation: the
171+
artifact is visible for review, but consumers see only the
172+
hand-authored VEX until the flag is flipped. This matches the
173+
roadmap's explicit guidance ("Run in parallel with hand-authored VEX
174+
for one release; diff the auto-set against .vex/ and confirm no
175+
surprises before flipping it on").
176+
177+
### Verification
178+
- `cargo fmt --all -- --check`: clean.
179+
- `cargo clippy --all-targets --all-features -- -D warnings`: clean.
180+
- `cargo test --lib`: 349 tests pass (329 pre-existing + 20 new for
181+
`auto_vex_presence`).
182+
- Smoke test: ran the bin against a synthetic Grype JSON (3 matches),
183+
synthetic SBOM (covering 1 of the 3 purls), and the current `.vex/`
184+
directory (covering 1 of the 3 CVEs). The bin emitted exactly the
185+
one statement for the remaining CVE whose purl was absent, and
186+
`vexctl merge` of the auto-presence output + `.vex/*.json`
187+
produced a 16-statement document with the auto statement correctly
188+
included.
189+
190+
### Impact
191+
- [ ] Breaking change
192+
- [x] Requires cluster rollout (no — CI only)
193+
- [ ] Config change only
194+
- [x] Documentation only
195+
196+
*(Rollout note: when maintainers want to flip `AUTO_VEX_PRESENCE=1`,
197+
that's a repo-variable change in GitHub settings, no code deploy.)*
198+
199+
---
200+
201+
## [2026-04-22 19:45] - Replace Python VEX tooling with vexctl (roadmap Phase 1)
202+
203+
**Author:** Erick Bourgeois
204+
205+
### Removed
206+
- `tools/` — entire directory deleted. Removed `assemble_openvex.py`,
207+
`validate_vex.py`, `validate-vex.sh`, `tests/assemble-openvex-tests.sh`,
208+
`tests/validate-vex-tests.sh`, and all 18 fixture directories under
209+
`tests/fixtures/`. ~400 LOC of bespoke Python + shell tests replaced
210+
by upstream `vexctl` invocations.
211+
212+
### Changed
213+
- `.vex/*.toml``.vex/*.json` (15 files): migrated every statement
214+
from the bespoke TOML dialect to the native OpenVEX v0.2.0 JSON
215+
shape. Each file is now a single-statement OpenVEX document that
216+
`vexctl merge` can consume directly; no translation layer.
217+
- `Makefile`: added `VEXCTL_VERSION ?= 0.4.1`, `vexctl-install` target
218+
(brew on macOS, pinned GitHub release tarball with checksum
219+
verification on Linux; errors on other OSes), `vex-validate`
220+
(parses every `.vex/*.json` via `vexctl merge` — successful parse
221+
is the validation), and `vex-assemble` (prints the merged document
222+
for local preview). `.PHONY` list updated.
223+
- `.github/workflows/build.yaml`:
224+
- `validate-vex` job: dropped Python setup + custom validator calls;
225+
now runs `make vexctl-install` then `make vex-validate`.
226+
- `build-vex` job: dropped Python setup + `assemble_openvex.py`;
227+
now runs `vexctl merge --id <per-event-id> --author <actor>
228+
.vex/*.json` directly. Per-event `@id` logic (release / push /
229+
PR) preserved verbatim. Cosign attestation, artifact upload,
230+
and `attest-build-provenance` steps unchanged.
231+
- PR `paths:` filter: removed `tools/**` (directory no longer
232+
exists); `.vex/**` retained.
233+
- `.vex/README.md`: rewritten to document native OpenVEX JSON
234+
authoring and `make vex-validate` / `make vex-assemble` instead of
235+
the old TOML schema and shell validator.
236+
- `docs/src/security/vex.md`: CI flow section updated — step 1
237+
("validate") and step 2 ("assemble") now describe `vexctl merge`
238+
behaviour; dropped the obsolete "cross-check with `vexctl validate`"
239+
step (vexctl has no `validate` subcommand). Maintainer-authoring
240+
section rewritten with the JSON shape in place of the TOML shape.
241+
- `.claude/SKILL.md`: release checklist item for VEX updated from the
242+
old three-script check to `make vex-validate`.
243+
244+
### Why
245+
Phase 1 of the automated VEX generation roadmap
246+
(`~/dev/roadmaps/5spot-automated-vex-generation.md`). The custom
247+
Python toolchain duplicated functionality that `vexctl` — the official
248+
OpenVEX CLI — provides upstream. Maintaining a bespoke TOML dialect,
249+
its validator, its assembler, and two fixture-based test suites
250+
added ~400 LOC of surface area with no offsetting benefit once we
251+
accept OpenVEX JSON as the authoring format. Replacing the whole
252+
stack with `vexctl merge` (which doubles as the validator via
253+
successful parse) removes the Python runtime from CI entirely,
254+
eliminates a class of drift between our validator and the upstream
255+
spec, and prepares the pipeline for Phases 2–3 (automated VEX
256+
generation from SBOM and call-graph reachability).
257+
258+
The TOML-over-JSON ergonomics hit is real but small: 15 files in
259+
`.vex/`, each ~15 lines of JSON, with no inline comments. In
260+
exchange, future maintainers work with the same format upstream
261+
tooling speaks and any OpenVEX-aware tool can consume the source
262+
directory directly.
263+
264+
### Verification
265+
- `make vex-validate` parses all 15 `.vex/*.json` successfully.
266+
- `vexctl merge --id <x> --author <y> .vex/*.json` produces a
267+
15-statement document structurally identical to what the Python
268+
assembler would have produced (confirmed via key-by-key diff of
269+
`@context`, `@id`, `author`, `version`, and every statement's
270+
`products`, `status`, `justification`, `impact_statement`,
271+
`action_statement`, `timestamp`).
272+
273+
### Impact
274+
- [ ] Breaking change
275+
- [ ] Requires cluster rollout
276+
- [ ] Config change only
277+
- [x] Documentation only
278+
279+
*(Not really "documentation only" — code deletion is substantive —
280+
but no runtime, CRD, or API-surface change. Closest pre-existing
281+
box.)*
282+
283+
---
284+
12285
## [2026-04-21 15:00] - Model emergency-reclaim in CALM — ADD retrofit
13286

14287
**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)