SBOM (Software Bill of Materials)
An SBOM is a machine-readable inventory of the components and versions inside a build output. What the formats record, and how a workflow generates one.
A software bill of materials, usually written SBOM, is a machine-readable inventory of the components and versions that went into a build output, together with the dependency relationships between them. It answers one question about an artifact that has already shipped: what is inside it, at which version, and under which identifier.
The value of that inventory shows up on the day an advisory lands for a widely used library. With an SBOM stored per release, finding affected artifacts is a query over documents you already have. Without one, the same question means rebuilding every release from source and hoping the dependency resolution reproduces.
Definition
An SBOM is a document produced by tooling during or after a build. Each entry names a component, pins a version, and carries identifiers that other systems can match against. The common identifier is a package URL, or purl, such as pkg:npm/[email protected], which encodes the ecosystem, the name, and the version in one string (package URL specification, checked on 2026-08-13). Entries reference each other through dependency relationships, so the document describes a graph rather than a flat list.
A baseline for what the document has to carry comes from the US National Telecommunications and Information Administration, which published a set of minimum elements in 2021 (minimum elements for an SBOM, checked on 2026-08-13).
| Minimum element | What it records |
|---|---|
| Supplier name | The party that produced each component |
| Component name | The name each component is published under |
| Version of the component | The exact version string, rather than a range |
| Other unique identifiers | Keys such as purl or CPE that support lookup |
| Dependency relationship | Which component includes which other component |
| Author of SBOM data | The tool or party that assembled the document |
| Timestamp | When the document was assembled |
The two formats in common use
Two specifications carry most of the traffic, and both are standardized.
| Format | Steward | Standardization | Usual suffix | Serializations |
|---|---|---|---|---|
| SPDX | Linux Foundation | Version 2.2.1 published as ISO/IEC 5962:2021 | .spdx.json | JSON, YAML, RDF, tag-value |
| CycloneDX | OWASP | Version 1.6 published as ECMA-424 | .cdx.json | JSON, XML, protobuf |
The SPDX specification and the CycloneDX specification (both checked on 2026-08-13) describe overlapping shapes: a document header, a component list, and relationships. Most generators can write either format from a single scan, so the choice is usually driven by what the consuming system parses. Converting between them is possible and lossy, because each format carries fields the other has no slot for.
When the inventory is taken
An SBOM describes the components a tool could see at the moment it ran, so the point in the pipeline where it is generated changes what ends up in the document. The US Cybersecurity and Infrastructure Security Agency names six types by that generation point (types of SBOM documents, checked on 2026-08-13).
| Type | Generated from | Typically misses |
|---|---|---|
| Design | Planned components, before code exists | Anything the implementation actually pulled in |
| Source | The repository and its dependency manifests | Components injected by the build itself |
| Build | The build process, as the artifact is assembled | Files added later by a deployment step |
| Analyzed | Inspection of the finished artifact | Provenance for components with stripped metadata |
| Deployed | The system as installed, including configuration | Code loaded only while the process runs |
| Runtime | Observation of the running system | Components on paths the observation never hit |
The build SBOM is the one a workflow produces, and it is the strongest of the six for release records, because the tool runs with both the source tree and the finished artifact in reach.
Two limits are worth stating plainly. An SBOM records inventory and carries no judgement about severity, so risk ranking happens later when a scanner joins the component list against advisory data, and the quality of that join depends on how well the identifiers resolve. An SBOM also says nothing about how the artifact was built or by whom; that record is a separate document, covered in provenance attestation.
Example
This release workflow builds a container image, generates an SBOM in SPDX JSON form from the pushed image, publishes the document as an attestation bound to the image digest, and attaches the same file to the GitHub release as a downloadable asset.
name: release
on:
push:
tags: ["v*"]
permissions:
contents: write
packages: write
id-token: write
attestations: write
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- id: push
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ghcr.io/${{ github.repository }}:${{ github.ref_name }}
- uses: anchore/sbom-action@v0
with:
image: ghcr.io/${{ github.repository }}@${{ steps.push.outputs.digest }}
format: spdx-json
output-file: sbom.spdx.json
- uses: actions/attest-sbom@v2
with:
subject-name: ghcr.io/${{ github.repository }}
subject-digest: ${{ steps.push.outputs.digest }}
sbom-path: sbom.spdx.json
push-to-registry: true
- run: gh release upload "${{ github.ref_name }}" sbom.spdx.json
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}Three details in that file do the work. The scan targets the digest in steps.push.outputs.digest rather than the tag, so the inventory describes the exact manifest this run pushed even if the tag moves afterwards. The format: spdx-json input selects the serialization, and the generator writes CycloneDX from the same scan when the value is cyclonedx-json (anchore/sbom-action, checked on 2026-08-13). The attestation step binds the document to that digest and to the workflow identity that produced it (actions/attest-sbom, checked on 2026-08-13), while gh release upload leaves a copy a human can download without registry credentials.
A consumer verifies the attested copy with the GitHub CLI, which resolves the image and checks the signature against the owner you name:
gh attestation verify oci://ghcr.io/acme/api@sha256:<digest> --owner acmeBuildKit can also produce the document during the build instead of scanning the result afterwards, which turns the whole thing into one flag on the build command (Docker SBOM attestations, checked on 2026-08-13):
docker buildx build --sbom=true --provenance=mode=max --push \
-t ghcr.io/acme/api:v1.4.0 .That form stores the SBOM in the registry beside the image rather than as a file in the job workspace, so nothing needs uploading. Pointing the same command at a remote builder is a builder configuration question, covered in the WarpBuild Docker builders documentation.
The three destinations behave differently once the run is over.
| Destination | Retrieved with | Survives a tag move | Available without registry access |
|---|---|---|---|
| Release asset | A browser or gh release download | Yes, the file is immutable once uploaded | Yes |
| Registry attestation | gh attestation verify, cosign, or docker buildx imagetools inspect | Yes, the subject is a digest | No |
| Workflow artifact | The run page, until retention expires | Yes | Yes, for users with repository access |
Related Terms
- Provenance attestations and the build facts they record: the signed statement about who built an artifact and from which source, which sits beside the inventory.
- How to generate an SBOM during a GitHub Actions build: the step-level recipe, including where the file lands and how it is verified.
- Running security scanning jobs in GitHub Actions: where scanning fits in a pipeline and how the job is kept off the critical path.
- Image digests and why a subject is pinned to one: the content address an attestation binds to, and how it differs from a tag.
- WarpBuild security documentation: compute isolation, storage handling, and compliance posture for runners.
- WarpBuild pricing: per minute rates by runner type.
FAQ
What is the difference between an SBOM and a vulnerability scan?
An SBOM is the inventory: the components, their versions, and the identifiers that name them. A vulnerability scan is a join between that inventory and an advisory database, performed at scan time. The same SBOM produces different findings a month later, because the advisory data moved while the artifact stayed the same.
Should a build emit SPDX or CycloneDX?
Emit whichever format the consumers of the document accept, and emit both when they disagree. SPDX 2.2.1 was published as ISO/IEC 5962:2021 and CycloneDX 1.6 was published as ECMA-424, so both are standardized and most generators can write either one from a single scan. Converters exist, and they lose fields that have no counterpart in the target format.
Does an SBOM need to be signed?
An unsigned SBOM is a JSON file that anyone with write access to the release can replace. Signing it, or publishing it as an attestation bound to the artifact digest, gives a consumer something to verify: that this document describes this exact artifact and was produced by the identity it claims.
Start with $10 in free credits
Change the runner label in your workflow and keep the rest of your GitHub Actions setup. Runner time is billed per minute.