SBOM & Compliance

How to Structure an SBOM Review Process

Build a repeatable SBOM review workflow that catches license risks, stale dependencies, and unexpected components before they ship to customers.

Nayan Dey
Senior Security Engineer
5 min read

Generating an SBOM is the easy part. Building a repeatable process to actually read those SBOMs, compare them release-to-release, and flag issues before a customer asks is where most teams stall. This tutorial walks through a four-stage review process you can run weekly: ingest, diff, triage, and sign-off. You will set up Syft and CycloneDX tooling, define review criteria that catch unexpected additions and risky licenses, and produce a lightweight review record that auditors can follow. By the end you will have a workflow that scales from a single service to a product line.

Prerequisites: syft 0.90+, cyclonedx-cli 0.24+, jq 1.6+, and a git repository to store review records. Time to complete: About 60 minutes to set up, 20 to 30 minutes per review cycle afterwards.

What should an SBOM review actually check?

A good review checks four things: component completeness, license exposure, version freshness, and unexpected additions. Component completeness asks whether the SBOM lists everything the build actually pulled in, including transitive dependencies and the base image for containers. License exposure looks for copyleft licenses in products you ship as proprietary. Version freshness surfaces components that have drifted more than two minor versions behind upstream. Unexpected additions are the scariest category: a new transitive dependency that nobody on the team added intentionally, often the signature of a dependency-confusion or typosquat attack.

Encode these four checks in a short checklist stored in your repo at docs/sbom-review.md. Every reviewer uses the same checklist, which keeps results comparable across releases and reviewers.

How do I ingest an SBOM consistently?

Standardize on one format and one generator. CycloneDX JSON is the most broadly supported format, and Syft produces clean, reproducible output across ecosystems. Generate SBOMs in your release pipeline and commit them to a dedicated sbom/ directory alongside the release tag:

syft packages dir:. -o cyclonedx-json=sbom/app-v2.4.0.cdx.json
git add sbom/app-v2.4.0.cdx.json
git commit -m "sbom: v2.4.0 release"

For container images, scan the final image rather than the source tree so you capture the base image and distro packages:

syft packages registry:ghcr.io/acme/app:v2.4.0 \
  -o cyclonedx-json=sbom/app-v2.4.0-image.cdx.json

Keeping both a source SBOM and an image SBOM makes the diff step far more useful, because you can tell whether a new component came from a dependency bump or from a base image change.

How do I diff two SBOMs to surface what changed?

Use cyclonedx-cli diff to produce a machine-readable diff between the previous and current release. This is the single highest-value step in the whole process:

cyclonedx-cli diff \
  --from-file sbom/app-v2.3.0.cdx.json \
  --to-file sbom/app-v2.4.0.cdx.json \
  --component-versions > sbom/diff-v2.3.0-to-v2.4.0.json

Extract the additions and version bumps with jq for human review:

jq '.componentVersionChanges[] | select(.status=="Added")' \
  sbom/diff-v2.3.0-to-v2.4.0.json
jq '.componentVersionChanges[] | select(.status=="Modified")' \
  sbom/diff-v2.3.0-to-v2.4.0.json

The reviewer's first question for every Added entry is: "Did someone on the team mean to add this?" If nobody can point to the commit or ticket that introduced it, that is your highest-priority investigation target. Pair the diff with git log --oneline v2.3.0..v2.4.0 -- package.json go.mod requirements.txt to correlate new components with the commits that landed them.

How do I triage license risk efficiently?

Run a license scan on the current SBOM and compare the result to your allowlist. Most teams maintain an allowlist of permissive licenses (MIT, Apache-2.0, BSD-3-Clause, ISC) and a block-list of strongly copyleft licenses (AGPL-3.0, GPL-3.0 for linked libraries):

cyclonedx-cli analyze --input-file sbom/app-v2.4.0.cdx.json \
  --multiple-component-versions --out sbom/analysis.json

jq -r '.components[] | [.name, .version, (.licenses[0].license.id // "UNKNOWN")] | @tsv' \
  sbom/app-v2.4.0.cdx.json | sort -u > sbom/licenses.tsv

Grep for flagged licenses:

grep -E "AGPL|GPL-3|SSPL|BUSL" sbom/licenses.tsv

Any match is a review item. The reviewer's job is to decide whether the component is dynamically linked, statically linked, or purely a build-time tool, because that materially changes the obligation. Record the decision in the review record so the next reviewer does not relitigate it.

How do I handle the sign-off and record?

Produce a short Markdown summary per review and commit it next to the SBOM. A minimal template:

# SBOM Review: app v2.4.0

- **Reviewer:** @shadab
- **Date:** 2023-07-17
- **SBOM:** sbom/app-v2.4.0.cdx.json
- **Diff:** sbom/diff-v2.3.0-to-v2.4.0.json

## Added components (verified)
- lodash 4.17.21 — transitive via @vendor/sdk 3.1, expected
- zod 3.21.4 — direct, ticket ENG-2140

## License exceptions
- None

## Open items
- libpq 15.2 pinned to minor version, next review scheduled 2023-08-01

Commit the review record with a signed commit. When an auditor asks "how did you know what shipped in v2.4.0," you can point to the SBOM, the diff, and the sign-off in one directory.

How often should I run this and who should own it?

Run a full review on every minor or major release and a diff-only review on every patch. Ownership works best when the security team defines the checklist and automation and the feature team runs the review, with security available for escalation. That keeps the knowledge close to the code and keeps the security team from becoming a bottleneck.

Automate what you can. CI should fail the build if a new component appears whose license is not in the allowlist, which means the diff step runs on every PR rather than only at release. Manual review then focuses on judgment calls, not on catching obvious policy breaks.

How Safeguard Helps

Safeguard ingests SBOMs on every push and runs the diff, license, and freshness checks automatically. Griffin AI narrates the review in plain language, highlighting added components that have low reputation scores, high reachability from your exposed code paths, or overdue version drift. Policy gates enforce license allowlists and block releases that introduce unexpected transitive components. The result is a review record generated per release with human sign-off required only on the genuinely novel findings, not on the repetitive work.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.