---
key: "sbom-generation"
title: "Generate an SBOM for the software you actually release"
url: "https://softwaresecurity.com/domains/supply-chain/sbom/generate-release-inventory/"
api: "https://softwaresecurity.com/api/v1/articles/sbom-generation.json"
domain: "supply-chain"
topic: "Software supply chain"
type: "Guide"
identifiers: ["A03:2025","V15.1.2"]
tags: ["SBOM"]
stages: ["release"]
roles: ["developer","appsec-engineer","platform-sre"]
license: "CC-BY-4.0"
license_url: "https://creativecommons.org/licenses/by/4.0/"
code_license: "MIT"
---

# Generate an SBOM for the software you actually release

Define the inventory boundary, compare build and artifact evidence, and connect the resulting SBOM to an immutable release.

Canonical URL: https://softwaresecurity.com/domains/supply-chain/sbom/generate-release-inventory/

## In short

Generate the SBOM from the artifact you ship, by digest, not from the repository alone. Scan the image with Syft or Trivy, keep package-manager SBOMs as build evidence, and reconcile components the scanner cannot see, such as statically linked libraries. Add the author and lifecycle phase, check the CISA 2026 minimum elements, then sign the SBOM as an attestation bound to the image digest and publish corrections as new versions.

- An SBOM describes one bounded object: name the image digest, platform and scan scope, because a lockfile and an image list different components.
- A clean scan is not a completeness proof: an image scanner cannot see a statically linked library unless build metadata records it.
- CISA's 2026 minimum elements (July 2026) add the author signature, generation context, tool name and version, component hash and license to the 2021 NTIA list.
- Default Syft 1.54 and Trivy 0.75 output leaves out the SBOM author, generation context and signature, so add them in the release job.
- Sign the SBOM as an attestation on the image digest; the signature authenticates the producer, not the inventory's accuracy.

## Recommended defaults

| Setting | Value | Note | Source |
| --- | --- | --- | --- |
| Scan target | The released image by digest, with `--platform` for each platform you ship | An index digest names several images; inventory each child you release. | Syft: supported targets and SBOM output |
| Syft layer scope | `--scope squashed` (the default) | Lists what the final filesystem contains; use `all-layers` only to investigate files deleted in later layers. | Syft: supported targets and SBOM output |
| Output format | CycloneDX 1.7 JSON, or SPDX 2.3 JSON where consumers require SPDX | Syft, Trivy and BuildKit still write SPDX 2.3, not 3.0.1. | CycloneDX specification overview, version 1.7 |
| SBOM author and generation context | `metadata.manufacturer` and `metadata.lifecycles` (`post-build` for an image scan) | — | CISA: 2026 Minimum Elements for a Software Bill of Materials |
| Component hash algorithm | SHA-256 or stronger, named with its IANA hash name | Trivy copies SHA-1 checksums from the Alpine package database. | CISA: 2026 Minimum Elements for a Software Bill of Materials |
| Attestation | `cosign attest --type cyclonedx` (predicate type `https://cyclonedx.org/bom`) on the image digest | — | Sigstore: verifying attestations with cosign |

## Verify it

1. Run `python3 cisa2026_fields.py cedar.release.cdx.json` and confirm that every document field is present except the signature, which you keep as an attestation.
2. Confirm that `metadata.component` names the released image digest, not a tag or an earlier build.
3. Check three anchors: a known OS package and a known application dependency are listed, and a development-only tool is absent.
4. Run `cosign verify-attestation --type cyclonedx` with the release workflow identity and issuer and expect exit code 0; with another identity, expect a failure.
5. Compare the Syft and Trivy SBOMs and explain every component that only one of them reports.

## What you will learn

- Distinguish source, build and runtime inventory boundaries.
- Reconcile conflicting discovery methods without treating missing detection as absence.
- Define independent completeness and artifact-binding checks.

Audience: Release, build and platform engineers who produce SBOMs, and security reviewers who accept them from suppliers

Before you start:

- Identify the shipped artifact and distinguish build, development and runtime dependencies.

Time: About 31 minutes: 9 to read and 22 for the exercise

## Define what the inventory describes

An SBOM is a claim about a bounded software object, produced by a particular observation method. A repository inventory, build-environment inventory and shipped-image inventory answer different questions. A lockfile may include test tools absent from production; an image may contain operating-system packages absent from that lockfile. Neither view is automatically wrong. The error is using one view as if it described another object without recording the boundary.

For the fictional Cedar image, the release question is which components are present in the `linux/amd64` runtime artifact, not everything used during development. Bind the inventory to the image digest, architecture and cataloging scope. A multi-platform image index can identify several distinct images; inventorying one child does not establish the contents of the others. The inventory producer should record generator version, configuration, time, source revision, image digest, scan errors, and known exclusions so later differences can be interpreted.

## Combine views without inventing certainty

Syft and Trivy inventory container images and filesystems; `npm sbom`, cyclonedx-py and the CycloneDX plugins for Maven and Gradle inventory the dependencies a package manager resolved. A release pipeline can scan the resolved image and keep the lockfile-derived inventory as separate supporting evidence. Resolve the immutable artifact first, pin the generator version, keep the raw output and record its actual format version: in October 2026 Syft 1.54 and Trivy 0.75 write CycloneDX 1.7, `npm sbom` writes CycloneDX 1.5, and Syft, Trivy and BuildKit write SPDX 2.3 rather than 3.0.1.

Cedar's evidence below contains three runtime findings and two build-only findings. The static executable contains an embedded parser identified in a build manifest even though the filesystem scanner sees only one executable. A filesystem scan that cannot extract its dependencies must not erase the manifest evidence. Conversely, a manifest saying a dependency was compiled does not establish that every function remains in the linked binary. Retain the claimed identity, discovery method and confidence rather than converting an inference into an observed fact. CycloneDX can represent components and dependency relationships; choose a schema/tool combination your consumers actually validate.

## Generate the inventory with Syft, Trivy and package managers

The script below scans one platform of the released image by digest with Syft, writing CycloneDX and SPDX from the same scan, and runs Trivy as a second generator so that differences in coverage become visible. It then records the SBOM author and the lifecycle phase, which neither generator fills in, and produces build-time SBOMs from npm and Python. Run against the `alpine:3.22` image, Syft and Trivy each reported the same 16 Alpine packages plus the operating system, and Syft also listed 81 files with SHA-1 and SHA-256 digests.

```bash
#!/usr/bin/env bash
set -euo pipefail
# The image you release, by digest (the multi-platform index digest)
IMAGE="ghcr.io/acme/cedar@${DIGEST:?set DIGEST to sha256:...}"

# Syft 1.54: one scan of the linux/amd64 image, CycloneDX 1.7 and SPDX 2.3 output
syft "$IMAGE" --platform linux/amd64 \
  -o cyclonedx-json=cedar.cdx.json -o spdx-json=cedar.spdx.json

# A second generator to compare coverage (Trivy 0.75 also writes CycloneDX 1.7)
trivy image --platform linux/amd64 --format cyclonedx --output cedar.trivy.cdx.json "$IMAGE"

# Record who produced the SBOM and the lifecycle phase it describes
jq '.metadata.manufacturer = {name: "Acme Release Engineering"}
  | .metadata.lifecycles = [{phase: "post-build"}]' cedar.cdx.json > cedar.release.cdx.json

# Build-time inventories from the package managers
npm sbom --sbom-format cyclonedx --sbom-type application --omit dev > web.cdx.json
cyclonedx-py environment --sv 1.7 -o api.cdx.json .venv/bin/python

# Report the CISA 2026 data fields the release SBOM carries
python3 cisa2026_fields.py cedar.release.cdx.json
```

| Generator | What it inventories | Output tested |
| --- | --- | --- |
| Syft 1.54 | Images, directories and archives: OS and language packages it can identify | CycloneDX 1.7, SPDX 2.3 |
| Trivy 0.75 | Images, filesystems and repositories | CycloneDX 1.7, SPDX 2.3 |
| `docker buildx build --sbom=true` | The image being built, scanned by Syft inside BuildKit | SPDX 2.3 attached to the image index |
| `npm sbom` | The resolved npm tree from `package-lock.json` | CycloneDX 1.5 |
| `cyclonedx-py environment` | Packages installed in a Python environment | CycloneDX 1.6 by default, 1.7 with `--sv 1.7` |

## Test completeness against independent known facts

Schema validity answers whether a document meets a data contract, and a field checker answers whether it carries the data a consumer requested. It does not answer whether the document omitted a package. Validate syntax, then compare against independent anchors: a known OS package, a known application dependency, and an intentionally absent development tool. Verify component versions against package metadata or build records, and make the root component or accompanying release manifest identify the target artifact. A document that includes all expected names but names yesterday's image still fails the release check.

In this fixture, a scanner output contains libc and the Cedar executable but omits an embedded parser. That is a coverage gap, not proof that the parser is absent. A reviewer reconciles the signed build manifest with the runtime inventory and adds a clearly attributed parser assertion, or records an unresolved completeness gap that blocks a consumer's required policy. Do not silently manufacture file paths or package-manager metadata for the embedded component. Missing evidence may warrant a qualified incomplete inventory; it never warrants a fictitious successful scan.

## Check the CISA 2026 minimum elements

On 2026-07-29 CISA and partner agencies, including NSA, BSI and ANSSI, published the 2026 Minimum Elements for an SBOM, replacing the 2021 NTIA list. It defines 17 data fields: nine about the document (author, author signature, data format name and version, generation context, timestamp, tool name and version, and SBOM version) and eight about each component (producer, name, version, identifiers, hash value and algorithm, license, and dependency relationship). It also sets practices: coverage of all components including transitive dependencies with no minimum depth, explicit marking of unknown or withheld information, prompt corrections, and machine-processable data.

The checker below reports which of those fields a CycloneDX 1.6 or 1.7 document carries. On the Syft 1.54 SBOM of `alpine:3.22` it found the author, signature and generation context missing, identifiers, names and versions on all 17 components, and no component with a SHA-256 or stronger hash: Syft records file digests, while Trivy copies SHA-1 checksums from the Alpine package database. Close the gaps in the release job, not by hand.

- SBOM Author: the organization that generated the SBOM, in `metadata.manufacturer`, not the tool vendor. BuildKit SBOMs name `Organization: Anchore, Inc` as a creator because Syft runs inside the build.
- SBOM Generation Context: `metadata.lifecycles` with `pre-build` for a lockfile, `build` for a build-time SBOM or `post-build` for an image scan.
- SBOM Author Signature: sign the exact SBOM bytes, for example as the attestation in the next section.
- Component Hash: where a package digest is unavailable, mark the value as unknown instead of leaving the field silently empty.
- Coverage: a statically linked or vendored component missing from the scan needs a manual, attributed entry, as in the exercise below.

```python
"""Report the CISA 2026 minimum data fields that a CycloneDX 1.6 or 1.7 JSON SBOM carries."""
import json
import sys

with open(sys.argv[1], encoding="utf-8") as f:
    bom = json.load(f)
meta = bom.get("metadata", {})
tools = meta.get("tools", {})
tools = tools.get("components", []) if isinstance(tools, dict) else tools
document = {
    "SBOM Author": meta.get("manufacturer") or meta.get("authors"),
    "SBOM Author Signature": bom.get("signature"),  # or a signed attestation kept with it
    "SBOM Data Format Name and Version": bom.get("bomFormat") and bom.get("specVersion"),
    "SBOM Generation Context": meta.get("lifecycles"),
    "SBOM Timestamp": meta.get("timestamp"),
    "SBOM Tool Name and Version": any(t.get("name") and t.get("version") for t in tools),
    "SBOM Version": bom.get("serialNumber") and bom.get("version"),
}
strong = {"SHA-256", "SHA-384", "SHA-512", "SHA3-256", "SHA3-384", "SHA3-512"}
in_graph = {d.get("ref") for d in bom.get("dependencies", [])}
component = {
    "Component Producer": lambda c: c.get("manufacturer") or c.get("supplier") or c.get("publisher"),
    "Component Name": lambda c: c.get("name"),
    "Component Version": lambda c: c.get("version"),
    "Component Identifiers": lambda c: any(c.get(k) for k in ("purl", "cpe", "swid", "omniborId", "swhid")),
    "Component Hash (SHA-256 or stronger)": lambda c: any(h.get("alg") in strong for h in c.get("hashes", [])),
    "Component License": lambda c: c.get("licenses"),
    "Component Dependency Relationship": lambda c: c.get("bom-ref") in in_graph,
}
# Packaged software only: file entries are evidence, not components.
components = [c for c in bom.get("components", []) if c.get("type") != "file"]
for name, value in document.items():
    print(f"{'present' if value else 'MISSING':>9}  {name}")
for name, test in component.items():
    print(f"{sum(1 for c in components if test(c)):>4}/{len(components):<4} {name}")
```

## Sign the SBOM and attach it to the image digest

An SBOM file sitting next to an image says nothing about which image it describes or who wrote it. `cosign attest` wraps the SBOM in an in-toto statement whose subject is the image digest, signs it with a keyless certificate for the release workflow, and stores it in the registry with the image. A consumer runs `cosign verify-attestation` with the expected workflow identity and issuer, so an SBOM attached by anyone else is rejected, and reads the verified SBOM from the output. On GitHub, `actions/attest` with `sbom-path` creates the same kind of attestation, which `gh attestation verify --predicate-type https://cyclonedx.org/bom` checks.

BuildKit can instead scan the image while building it and attach an SPDX SBOM to the image index with `--sbom=true`. That SBOM is part of the build output but carries no separate signature, so sign the index or verify the build provenance before relying on it.

```bash
# Release job (permissions: id-token: write, packages: write): sign the SBOM as an
# in-toto attestation bound to the image digest, with a keyless certificate
cosign attest --yes --type cyclonedx --predicate cedar.release.cdx.json "$IMAGE"

# Consumer: accept SBOMs only from the release workflow, one verified SBOM per line
cosign verify-attestation --type cyclonedx \
  --certificate-identity https://github.com/acme/cedar/.github/workflows/release.yml@refs/heads/main \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  "$IMAGE" | jq -c '.payload | @base64d | fromjson | .predicate' > verified-sboms.jsonl

# BuildKit alternative: scan during the build and attach an SPDX SBOM to the image index
docker buildx build --sbom=true --provenance=mode=max -t ghcr.io/acme/cedar:2.3 --push .
docker buildx imagetools inspect ghcr.io/acme/cedar:2.3 --format '{{ json .SBOM.SPDX }}'
```

## Publish the inventory as part of a release record

A useful release bundle joins artifact digest, SBOM digest, provenance reference, generator configuration and known limitations. Signing an SBOM authenticates its issuer and bytes; it cannot make a wrong inventory correct. A consumer still checks the subject binding and the supplier's credibility. An unsigned checksum delivered beside an attacker-controlled SBOM is not an independent authenticity guarantee. Store immutable versions and distribute corrections with a new document revision linked to the same affected artifact. The same release SBOM drives license checks and third-party notices: /domains/supply-chain/dependencies/open-source-license-compliance/.

There is a practical disclosure tradeoff: internal build paths or private package names can be sensitive, while withholding them can weaken a consumer's analysis. Produce audience-specific documents through an explicit transformation and list the excluded fields and remaining completeness claim. Do not distribute a redacted document as complete by default. A release inventory is also a snapshot; dynamically downloaded plugins and runtime services require additional scope records. The SBOM inventory lab at /labs/sbom-inventory/ practices the same comparison in code, and the release-verification project on this page builds an SBOM into a signed release; every observation needed for the exercise decision is supplied here.

## Know which requirement the SBOM must meet

Customers and regulators ask for SBOMs against different baselines, so record which one a document was produced for. The EU Cyber Resilience Act obligations are covered in /frameworks/product-security-obligations/.

| Requirement | Status on 2026-10-10 | What it asks for |
| --- | --- | --- |
| CISA 2026 Minimum Elements for an SBOM | Published 2026-07-29; replaces the 2021 NTIA minimum elements | 17 data fields, including author signature, component hash and license, plus coverage of transitive dependencies and machine-processable formats. |
| EU Cyber Resilience Act, Annex I Part II point 1 | Applies from 2027-12-11 | An SBOM in a commonly used, machine-readable format covering at least the top-level dependencies, kept in the technical documentation and given to market surveillance authorities on reasoned request; publishing it to users is optional. |
| BSI TR-03183-2 | Version 2.1.0 | German technical guideline for SBOM content under the CRA, with a mapping of its fields to CycloneDX and SPDX. |

## Guided practice

Cedar 2.3 ships one `linux/amd64` image. Decide whether a candidate SBOM is ready for a customer who requires all known shipped components.

### Supplied material

```text
Artifact: cedar:2.3 linux/amd64, digest IMG23 (training label).
E1 runtime filesystem scan: libc 1.2.5; cedar executable 2.3. No parsekit component detected. Scan completed without errors.
E2 signed build manifest for IMG23: cedar statically links pkg:generic/parsekit@4.1.0; build uses pkg:generic/compiler@9.2; no compiler files copied into final image.
E3 final image file list: /app/cedar, /lib/libc.so, /etc/cedar.conf; no test runner or compiler.
E4 lockfile: parsekit 4.1.0 runtime; testkit 7.0 dev-only.
Candidate BOM: root IMG22, libc 1.2.5, cedar 2.3, testkit 7.0. Claims complete.
Customer policy: exact artifact binding; known shipped components included; exclusions and inferred identities disclosed.
Owner: fictional release engineer Sol. Generator profile G4: runtime filesystem, embedded extraction unsupported.
```

### 1. Fix the boundary

Specify the exact target and exclude build-only records with reasons.

Worked answer: Target is IMG23, `linux/amd64` runtime. Compiler 9.2 and testkit 7.0 are build/development inventory entries; E2/E3 establish they are not shipped here. Keep them in supporting build evidence, not as runtime components.

### 2. Reconcile the gap

Compare E1 and E2. Decide whether parsekit is absent or missing from detection.

Worked answer: The supplied manifest asserts parsekit 4.1.0 is embedded in IMG23; E1 lacks extraction support. Record parsekit with manifest-based evidence E2 and confidence limitation. A successful scan is not a completeness proof.

### 3. Correct the candidate

List all runtime component identities and the root change.

Worked answer: Change IMG22 to IMG23; retain cedar 2.3 and libc 1.2.5; add parsekit 4.1.0 with E2 attribution; remove testkit from runtime scope. Known runtime count is three components, excluding the artifact root representation.

### 4. Write acceptance tests

Define three tests and their expected results without inventing execution.

Worked answer: Proposed tests: root equals IMG23; runtime contains libc/cedar/parsekit with stated versions; runtime excludes compiler/testkit. All should pass on the corrected model. Schema validation is additionally required but cannot be claimed from these desk facts.

### 5. Publish the qualified claim

Write the release record and customer-facing limitation.

Worked answer: Sol’s draft record binds IMG23 to corrected SBOM revision 2, E1–E4 and G4. Completeness is limited to known components in one architecture; parsekit is manifest-derived; runtime downloads were not assessed. Customer acceptance requires validating and authenticating the actual document.

Deliverable: A corrected three-component inventory and a release record showing source evidence for each inclusion and exclusion.

Scope: IMG23 is a training identifier, not a cryptographic digest. The exercise models reconciliation and does not run a cataloger or validate a generated schema.

## Executable practice

- [SBOM completeness lab: Find the dependencies missing from a release SBOM](https://softwaresecurity.com/labs/sbom-inventory/)
- [Build and verify a signed software release: project ZIP](https://softwaresecurity.com/downloads/projects/release-verification-bd914fcbede61c32.zip) — Node.js 22+ · no dependencies

Articles and teaching material: CC BY 4.0. Original executable code: MIT. Attribution: Software Security.

## Sources

- [Syft: supported targets and SBOM output](https://github.com/anchore/syft)
- [CycloneDX specification overview, version 1.7](https://cyclonedx.org/specification/overview/). Version 1.7 (October 2025).
- [SPDX specification 3.0.1](https://spdx.github.io/spdx-spec/v3.0.1/)
- [CISA: 2026 Minimum Elements for a Software Bill of Materials](https://www.cisa.gov/resources-tools/resources/2026-minimum-elements-software-bill-materials-sbom). 29 July 2026.
- [Trivy: SBOM generation](https://trivy.dev/docs/latest/supply-chain/sbom/)
- [Docker Docs: SBOM attestations](https://docs.docker.com/build/metadata/attestations/sbom/)
- [Sigstore: verifying attestations with cosign](https://docs.sigstore.dev/cosign/verifying/attestation/)
- [npm Docs: npm sbom](https://docs.npmjs.com/cli/v11/commands/npm-sbom/). npm CLI v11.
- [CycloneDX Python SBOM tool](https://cyclonedx-bom-tool.readthedocs.io/en/latest/). CycloneDX Python 7.5.0.
- [Regulation (EU) 2024/2847 (Cyber Resilience Act)](https://eur-lex.europa.eu/eli/reg/2024/2847/oj)
- [BSI TR-03183: cyber resilience requirements](https://www.bsi.bund.de/EN/Themen/Unternehmen-und-Organisationen/Standards-und-Zertifizierung/Technische-Richtlinien/TR-nach-Thema-sortiert/tr03183/TR-03183_node.html). Part 2 (SBOM) version 2.1.0.