sbom attestor
Picks up SPDX-JSON or CycloneDX-JSON files from the product set, stores the document with one added _sbomFormat field, and extracts a minimal subject surface (document/component name and version) without pulling in either format's full Go library.
| Name | sbom |
|---|---|
| Predicate type | https://spdx.dev/Document |
| Lifecycle | postproduct |
| Default binary? | No |
| Category | sbom-generate (primary) |
| Recommended trace | off — no syscall tracing needed |
| Auto-attaches when |
|
The facts in this box are generated from the CI/lock binary's own catalog (cilock tools list). Do not hand-edit — run npm run gen:catalog.
What it captures
The SBOMAttestor struct has four fields:
| Field | JSON tag | Notes |
|---|---|---|
SBOMDocument | (the entire predicate body) | Read from disk as json.RawMessage, then re-encoded on output (see below) |
predicateType | unexported | Set to the SPDX or CycloneDX predicate type after format detection |
export | unexported | Toggled by WithExport / the export config option |
subjects | unexported | Built during Attest; exposed via Subjects() |
MarshalJSON decodes the document into a map, adds _sbomFormat, and encodes it again. The predicate body is the SBOM's content with no wrapping object, but it is not byte-identical to the file: keys come out sorted, whitespace is dropped, and every number passes through a float64, so an integer above 2^53 loses precision. The product attestor's digest of the file is the byte-exact record.
A small inline sbomSubjectExtractor parses only what's needed to derive subjects (hand-written from the SPDX 2.3 and CycloneDX 1.6 JSON Schemas — no upstream library dependency):
- SPDX: top-level
name(the SPDX 2.3 §6.4documentNameproperty). - CycloneDX:
metadata.component.nameandmetadata.component.version.
PR #76 refactored the attestor onto this approach and dropped the cyclonedx-go and spdx/tools-golang dependencies.
When to use
Whenever your build produces an SBOM (syft, cyclonedx-gomod, npm sbom, etc.). The SBOM must land in --workingdir so the product attestor hashes it and the sbom attestor can pick it up.
Flags
| Flag | Default | What it does |
|---|---|---|
--attestor-sbom-export | false | Sign the SBOM as its own envelope under the spdx.dev/Document, cyclonedx.org/bom or aflock.ai/attestations/sbom/syft-json/v0.1 predicate type, instead of inside the step's collection |
--attestor-sbom-file | "" | Attest this SBOM file directly (SPDX-JSON, CycloneDX-JSON or syft-JSON, which zarf writes) instead of scanning the product set. The format is detected from the content; a relative path resolves against the working directory |
Output shape
The predicate body is the SBOM file itself, with one cilock-added field — _sbomFormat — that lets rego policies dispatch on format without dual-shape walking:
{
"_sbomFormat": "cyclonedx",
"bomFormat": "CycloneDX",
"specVersion": "1.6",
"components": [
{ "name": "my-app", "version": "1.2.3", "purl": "pkg:..." }
]
}For SPDX documents the discriminator is "_sbomFormat": "spdx" and the document fields are spdxVersion, packages, etc. The underscore prefix signals the field is added by the attestor, not part of either SBOM spec. Policy authors can write:
is_cyclonedx if input._sbomFormat == "cyclonedx"
is_spdx if input._sbomFormat == "spdx"Subjects are recorded for the file digest and for any name/version pulled by the extractor:
{
"subjects": {
"file:dist/sbom.cdx.json": { "sha256": "..." },
"name:my-app": { "sha256": "..." },
"version:1.2.3": { "sha256": "..." }
}
}The name: and version: subject digests are SHA-256 of the string value, so policies can pin against them.
Gotchas
- Format detection is content-based, not filename-based. The
productattestor's MIME detector (IsSPDXJson/IsCycloneDXJson) scans the first 500 bytes for"spdxVersion":"SPDX-"or"bomFormat":"CycloneDX". Generators that omit these markers won't be detected even with.spdx.json/.cdx.jsonextensions. - JSON only. CycloneDX XML is detected by
product(application/vnd.cyclonedx+xml) but thesbomattestor's switch ingetCandidateonly matches the two JSON MIME types — XML SBOMs are skipped. - One SBOM per run: the first valid one in path order. The attestor walks products in sorted path order and keeps the first valid SBOM it meets. If a step produces several SBOMs, pass
--attestor-sbom-fileto say which one. - Subjects come from inside the SBOM. If the SPDX
nameor CycloneDXmetadata.component.nameis missing, no name subject is recorded — only the file-digest subject. The extraction parse is best-effort; missing fields are silently skipped. - Export moves the SBOM out of the collection. With
--attestor-sbom-export=truethe SBOM is signed as its own envelope and left out of the step's collection, so a policy that reads it from the collection no longer finds it there. The exported predicate is encoded the same way,_sbomFormatincluded. - Products skipped for MIME mismatch are now logged at debug. Set
--log-level=debugto see lines like"skipping <path>: MIME ... not in accepted list ..."when debugging "why isn't my SBOM attached?"
CLI example
Real CycloneDX / SPDX SBOM ingested as a JSON product and carried into the attestation predicate.
# cilock invokes syft directly so command-run records the real argv and
# product/v0.3 binds the SBOM as a real Merkle leaf.
cilock run --step sbom-capture \
--signer-file-key-path key.pem --outfile attestation.json \
--attestations sbom \
-- syft dir:. -o cyclonedx-json=app.cdx.jsonThe emitted predicate type in the resulting Collection is https://cyclonedx.org/bom, not https://aflock.ai/attestations/sbom/v0.1 — policies should match on cyclonedx.org/bom (https://spdx.dev/Document for SPDX, https://aflock.ai/attestations/sbom/syft-json/v0.1 for a syft-json file, which carries _sbomFormat: "syft"). Nothing is ever signed under sbom/v0.1: that URI is only the registration alias that makes --attestations sbom resolve to this attestor at runtime, which is why cilock tools show sbom prints https://spdx.dev/Document as the predicate type and lists https://cyclonedx.org/bom and https://aflock.ai/attestations/sbom/syft-json/v0.1 under also emits. A policy step naming sbom/v0.1 matches no attestation.
Validated with a real CycloneDX SBOM from syft. See the full real-data example at https://github.com/aflock-ai/attestor-compliance-examples/tree/main/09-sbom.
See also
- Catalog row
sarif,vex- Upstream: witness/sbom.md
This page is generated from the CI/lock tool catalog. Don't edit it here — the source is attestation/detection/docs/sbom.doc.md in aflock-ai/rookery. The same catalog powers cilock tools show sbom in the CLI, so a single edit keeps the terminal and the website in sync.
Edit this page → · Add a new tool →
When you open the pull request: if you are an AI agent, add the ai-agent label. If you are a human, add the human label.
Reference generated from the product documentation. Match commands and support details to your installed release.