Software bill of materials
The components a build says it ships, read from the bill of materials it already wrote (CycloneDX or SPDX, in JSON, XML or tag-value) and normalized into one list with the licence each component names.
| Facts key | sbom |
| Version | v1 |
| Script | sbom.py |
| Timeout | 60 seconds |
Inputs
| Input | Description | Default | Environment |
|---|---|---|---|
reports | Comma separated globs matched against every file in the working directory and against bare file names, naming the bills of materials to read. | bom.json,bom.xml,*.cdx.json,*.cdx.xml,cyclonedx*.json,cyclonedx*.xml,sbom*.json,*.spdx.json,sbom*.spdx | GUARDRAIL_INPUT_REPORTS |
maxComponents | Maximum number of components to carry in the facts. The counts still include components that are dropped. | 500 | GUARDRAIL_INPUT_MAXCOMPONENTS |
When a guardrail declares an input with the same name, it passes its value through. That means you set these values in the guardrail's configuration in buildnote.json.
A guardrail asks for these facts by name and reads them back by the same name:
{
"collect": ["sbom"]
}sbom = guardrail.facts("sbom")Facts
These are the fields of the document sbom collects. In a path, [] means each entry of the list before it, and [path] means a key of the object before it.
| Fact | Meaning |
|---|---|
source | report when a bill of materials was read, none when there was none. |
reason | Why nothing was read, present only when source is none. |
reports | Every bill of materials that was read, in path order. |
reports[].path | Path of the bill of materials, relative to the directory the CLI runs in. |
reports[].modified | When it was last written, ISO 8601, so a bill of materials older than the build is recognisable. |
reports[].format | Format it was read as: cyclonedx or spdx. |
format | Format of the first bill of materials read, cyclonedx or spdx. Each report carries its own in reports[].format. |
specVersion | Specification version that first bill of materials declares, such as 1.5 or SPDX-2.3, or null when it declares none. |
counts.total | Components across every bill of materials that was read. |
counts.licensed | Components naming at least one licence. |
counts.unlicensed | Components naming none. |
licenses | Licence identifier to the number of components naming it. A component naming two licences counts under both. |
components | The components themselves, up to maxComponents. |
components[].name | Name of the component, as the bill of materials names it. |
components[].version | Version of the component, empty when it carries none. |
components[].purl | Package URL of the component, or null when it carries none. |
components[].type | What the component is, such as library, application or container, or null. |
components[].licenses | Every licence the component names, by identifier or expression. |
dropped | Components left out because maxComponents was reached. |
If a collector can't finish, it prints what it gathered so far along with an incomplete key that says why. Facts after the point where it stopped are missing, so a check that depends on them should read incomplete first.
Example facts
Here are the facts the sbom collector gathers from an example project:
{
"source": "report",
"reports": [
{
"path": "bom.json",
"modified": "2026-03-04T10:15:00Z",
"format": "cyclonedx"
}
],
"format": "cyclonedx",
"specVersion": "1.5",
"counts": {
"total": 4,
"licensed": 3,
"unlicensed": 1
},
"licenses": {
"Apache-2.0": 2,
"MIT": 1
},
"components": [
{
"name": "http4k-core",
"version": "6.57.2.0",
"purl": "pkg:maven/org.http4k/http4k-core@6.57.2.0",
"type": "library",
"licenses": [
"Apache-2.0"
]
},
{
"name": "slf4j-api",
"version": "2.0.13",
"purl": "pkg:maven/org.slf4j/slf4j-api@2.0.13",
"type": "library",
"licenses": [
"MIT"
]
},
{
"name": "guava",
"version": "33.2.0-jre",
"purl": "pkg:maven/com.google.guava/guava@33.2.0-jre",
"type": "library",
"licenses": [
"Apache-2.0"
]
},
{
"name": "logback-classic",
"version": "1.5.6",
"purl": "pkg:maven/ch.qos.logback/logback-classic@1.5.6",
"type": "library",
"licenses": []
}
],
"dropped": 0
}