feat(sbom): CycloneDX 1.5 build-SBOM emission (rivet #107)#129
Merged
Conversation
Add `synth compile --sbom`, which writes a CycloneDX 1.5 JSON SBOM next
to the output ELF (`<output>.cdx.json`, or an explicit path). The SBOM is
a *build* SBOM scoped to what synth actually knows as a compiler — not a
transitive dependency scan (synth is not a linker):
- `metadata.tools` — the synth compiler ("what built it").
- the input WASM module — component with SHA-256 + byte size.
- the output ELF — component with SHA-256, size, target triple, backend.
- the WASM module's imports — each becomes a component, linked into the
output ELF via the CycloneDX `dependencies` graph.
The document is deterministic for a given input except `metadata.timestamp`
(wall-clock by design); the `serialNumber` urn:uuid is derived from the
output ELF digest so it too is reproducible.
This is synth's contribution to the PulseEngine supply-chain chain: the
emitted file is the artifact rivet #107's `sbom-record` ingests via
`rivet import --format cyclonedx`, sitting alongside synth's existing
`safety-manifest.json` and the release pipeline's SLSA provenance.
New `crates/synth-core/src/sbom.rs` mirrors `safety_manifest.rs`; adds the
lightweight `sha2` dependency for component digests. `--sbom` is a flag on
`compile` rather than a subcommand because synth has every input in hand
there (WASM bytes, decoded imports, target, backend, fresh ELF). Docs in
`docs/sbom.md`; release-pipeline auto-emit noted as a follow-up.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
The SBOM module (`synth-core/src/sbom.rs`) uses `sha2` for CycloneDX component digests and `serde_json` for serialization. Both are declared in `synth-core/Cargo.toml`, so the cargo build is fine — but synth's Bazel build uses an explicit `crate.spec` list in MODULE.bazel and a hand-maintained `crates/BUILD.bazel`, neither of which auto-derives from Cargo.toml. CI caught it: `Bazel Build & Proofs` failed compiling the synth-core rlib with `unresolved import sha2` / `unresolved module serde_json`. Fixes: - MODULE.bazel — add `crate.spec(package = "sha2", version = "0.10")`. - crates/BUILD.bazel — add `@crates//:serde_json` and `@crates//:sha2` to the `synth-core` rust_library deps. (`serde_json` was already in the `@crates//` set — used by other targets — just not wired to synth-core.)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds CycloneDX 1.5 SBOM emission to synth — the synth-side slice of pulseengine/rivet#107 (Supply chain integration: Sigil attestations + SBOM/AIBOM + Rivet traceability bridge).
When synth compiles a WASM module to an ELF binary, it can now emit a build SBOM documenting exactly what went into that binary. That
.cdx.jsonis the artifact rivet'ssbom-recordtype ingests viarivet import --format cyclonedx.What the SBOM contains
A build SBOM — not a transitive dependency scan (synth is a compiler, not a linker):
metadata.tools— the synth compiler + version ("what built it")dependenciesgraph linking the ELF → WASM module → importsAll required CycloneDX 1.5 fields present:
bomFormat,specVersion,serialNumber(urn:uuid, derived from the ELF digest so it's reproducible),metadata,components,dependencies.CLI surface
A
--sbomflag onsynth compile(not a subcommand). Bare--sbomwrites<output>.cdx.json;--sbom PATHwrites verbatim. Chosen over a subcommand becausecompilealready holds every input — the post-decode WASM bytes, decoded imports, target triple, backend, fresh ELF — that a re-deriving subcommand couldn't see. Threaded through both the single-function and--all-exportspaths; demo compilations (no input file) warn and skip.rivet #107 mapping
The emitted
.cdx.jsonis exactly what rivet'ssbom-recordartifact (type: sbom-record,format: cyclonedx) points at viasbom-ref. Documented indocs/sbom.md.Implementation
crates/synth-core/src/sbom.rs(629 lines) —CycloneDxSbomstruct, serde serialization, 10 unit tests asserting the required-field shape (parse the JSON back withserde_json).crates/synth-cli/src/main.rs— the--sbomflag, both compile paths.sha2 = "0.10"(workspace + synth-core), for component digests. +12 Cargo.lock lines.docs/sbom.md— what the SBOM contains, the rivet linkage, therivet importconsumption path.Determinism
Two runs over the same input produce byte-identical SBOMs except
metadata.timestamp(wall-clock, by design for SBOMs).Validation
cargo test --workspace --exclude synth-verify— 1203 passed, 0 failed (10 new SBOM tests).cargo clippy --workspace --exclude synth-verify --all-targets -- -D warnings— clean.cargo fmt --check— clean.Follow-ups (out of scope here)
--sbomintorelease.ymland upload the.cdx.jsonas a signed release asset (noted as "Phase 6" indocs/release-process.md; the compiler side is done).--sbom-format spdx) if a downstream consumer needs it."unknown"pending WIT/Component-Model version metadata.Test plan
synth compile in.wat out.elf --sbomproduces a CycloneDX 1.5out.elf.cdx.json🤖 Generated with Claude Code