Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/holger

.nornir/sbom-hosting-guide.md

SBOM hosting — guide

Class: guide · Function: sbom. How to attach an SBOM to an artifact and pull it back over each surface. The design is sbom-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

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.