SBOM hosting — guide
Class:
guide· Function:sbom. How to attach an SBOM to an artifact and pull it back over each surface. The design issbom-hosting-design.md.
Holger hosts a CycloneDX SBOM attached to an artifact coordinate (repository, namespace/name/version) and serves it back byte-identical. An SBOM sits next to the package it describes.
Attach + fetch with the CLI
The CLI drives the same on-disk store the server hosts (<data_root>/holger-sboms, derived from the config's audit dir), so a CLI-attached SBOM is immediately served by the RPC + HTTP door — no running server needed.
# Attach a CycloneDX document to serde@1.0.0 in the `rust-dev` repo:
holger-server sbom attach \
--config holger.ron \
--repo rust-dev --name serde --version 1.0.0 \
--file serde-1.0.0.cdx.json
# → ✓ attached SBOM (1234 bytes) to [rust-dev] -/serde@1.0.0 — content b3:…
# Fetch it back (to a file, or stdout when --out is omitted):
holger-server sbom fetch \
--config holger.ron \
--repo rust-dev --name serde --version 1.0.0 \
--out serde-1.0.0.cdx.json
Maven-style coordinates carry a namespace (groupId): add --namespace org.example (alias --group). Omit it for namespace-less ecosystems (Cargo, PyPI). A re-attach overwrites (last-wins) — regenerate the SBOM and attach again. Fetching a coordinate with no SBOM exits non-zero with no SBOM hosted for ….
Fetch over HTTP (read-only door)
curl 'https://holger.example/-/sbom?repo=rust-dev&name=serde&version=1.0.0'
# 200 application/json → the exact attached bytes
# 404 → no SBOM hosted for that coordinate
# 400 → missing repo / name / version
namespace (alias group) is optional. The door is read-only — attach over gRPC or the CLI.
Attach + fetch over gRPC (SbomService)
rpc AttachSbom(AttachSbomRequest) returns (AttachSbomResponse); // write-gated
rpc FetchSbom(FetchSbomRequest) returns (FetchSbomResponse); // open read
AttachSbomRequest { repository, artifact{namespace,name,version}, document }→{ success, message, content_address }(blake3 hex of the document). Requires writer/admin when a role policy is configured (fail-closed), scoped torepository.FetchSbomRequest { repository, artifact }→{ document, size_bytes, content_address }, orNOT_FOUNDwhen nothing is attached.
Building a document from the shared model
If you hold the structured component/vuln set rather than a ready document, build it through the one seam that emits modgunn's CycloneDX 1.5 verbatim:
use server_lib::sbom::{build_cyclonedx, SbomInput, SbomComponent};
let doc: Vec<u8> = build_cyclonedx(&SbomInput {
subject: "rust-dev".into(),
components: vec![SbomComponent {
ecosystem: "cargo".into(), name: "serde".into(),
version: "1.0.0".into(), license: Some("MIT OR Apache-2.0".into()),
}],
vulns: vec![],
});
// then attach `doc` via the CLI (--file) / gRPC (document).
Where it lives
Documents are files under <data_root>/holger-sboms/<repo>/<coord>.sbom.json. data_root is the parent of the audit dir in your RON config, so back up / carry the SBOM store the same way you carry the audit log and promote store.