131% more reported CVEs per day in 2026 than last year.

SBOM

SBOM generation across engineering teams: one policy, every pipeline

Most organizations do not have an SBOM problem; they have six SBOM processes. Fix the output first: one format, one set of fields, one moment of generation, one store. Then move each team's pipeline to meet it, preferring generation inside the release build. Customers, auditors and regulators such as the CRA look at the SBOM and at the process behind it, not at which scanner you bought.

Updated

Why per-team scanners do not add up to an SBOM programme

Point five tools at the same product and you get five SBOMs. The differences are structural, not bugs:

  • Different inputs, read at different times. One tool reads the lockfile, another the installed dependency tree, another the container image, another the binary. Each sees a different slice of the product.
  • Different names for the same thing. One identifies components by package URL, another by CPE, another by file name only. Two SBOMs that describe the same library cannot be joined on a common key.
  • Different depth. Some list only direct dependencies, some the full transitive tree, and some also list build tools. Why generators disagree on the same repository is shown in open source SBOM generators.
  • Some tools answer a different question. Infrastructure vulnerability scanners report what is installed on running hosts. That is useful for operations, and it is not a description of the product you ship.

The consequence: you cannot merge, deduplicate or compare SBOMs across products, and you cannot show an auditor one process, because there is none.

Method Input it reads When What it misses Typical identifier
Lockfile or manifest scanner Lockfiles and manifests in the checkout Before or after the build Anything no lockfile lists: install hooks, downloads by build scripts, base images Package URL
Container image scanner Package databases and files in the image After the build Build-only tools, compiled-in code, anything without a package record Package URL for OS and language packages
Binary or firmware scanner Strings and signatures in the binary After the build Certainty: versions and components are inferred CPE or inferred name
Host vulnerability scanner Installed software on running servers In operation The product as shipped to customers CPE or vendor product name
Build-time recording What the release build fetched During the build Software you did not build; what it could not see is flagged as incomplete Package URL

The SBOM policy: what to standardize

Standardize the SBOM, not the scanner. The policy fixes the output and the process; each pipeline meets it with the best generator it can run. Every row below is something you can check.

Requirement The rule Why How to check it
Scope Every product release that ships to a customer, including firmware and container images. Internal tools optional. Customers and regulators ask about what you ship. Each release in the release register has an SBOM.
Format and version One primary format, CycloneDX or SPDX, with a minimum version. Accept the other on request. One format means one parser, one validator and one store. BSI TR-03183-2 asks for CycloneDX 1.6 or later or SPDX 3.0.1 or later, a sensible floor for EU customers. Schema validation reports format and version.
Minimum fields A package URL on every component that has one, a CPE only where none exists, supplier, version, declared license, dependency relationships, tool name and version, timestamp and generation context. These are what matching, license review and audits use. Align them with the minimum elements on what is an SBOM. A field check in the pipeline counts missing values.
When generated In the CI build that produces the release, every time. Not at audit time, not on a laptop. A rebuild is not the same build; see build-time SBOMs. The SBOM's build ID matches the release's build ID.
Depth The full transitive tree, not top-level only. The CRA's minimum is the top-level dependencies; vulnerabilities and customers go deeper. Dependency relationships exist below the first level.
Where stored With the release artifact, retrievable by product and version. An SBOM nobody can find when an advisory lands is not evidence. Fetch the SBOM for a random release by product and version.
Retention At least the product's support period plus the documentation retention your regulations require. The CRA sets a retention period for technical documentation, and the SBOM is part of it. Compare the policy period with each tool's retention.
Validation Every SBOM is schema-validated in the pipeline; an invalid one fails the build or the release gate. Invalid SBOMs fail silently in the customer's tooling. The release gate log shows a validation step.
Ownership The product team owns generation; product security owns the policy, the validation rules and coverage reporting. Generation needs the pipeline owner; consistency needs one owner of the rules. Each pipeline names an owner.
Exceptions A named, time-boxed exception register: team, reason, end date. The policy survives teams that cannot comply yet. No exception is past its end date.

The checklist in SBOM best practices covers the rules for a single SBOM; this policy is the layer above them. Each team declares how its pipeline meets the policy, in a file next to the pipeline:

# sbom-policy.yaml, one per release pipeline
product: gateway-firmware
owner: team-gateway
format: cyclonedx
spec_version: "1.6"
generated_in: release-build
generator: build-time-recording
store: artifact-registry
validation: schema, fail-on-errors
exception: none

Generate in the build, not per team after the fact

Build-time generation is the standard to aim for. It describes the build that shipped, it includes hidden dependencies that no lockfile lists, and one mechanism produces the same shape of SBOM for every team on that pipeline. The difference is easiest to explain with a kitchen: a scanner tastes the soup and guesses the recipe; a recorder watches what goes into the kitchen.

A scanner is still the right tool for images you did not build, for third-party binaries, and for legacy products with no CI. Say so in the policy, and record the generation context, so a reader knows what each SBOM saw.

Situation Generation method Generation context to record
Release built in CI you control Record the build, or run a generator inside the release job "Recorded during build" or "after install, from files"
Container image built in CI Record the image build; scan the result for its OS packages if the recorder does not cover them Build ID and image digest
Third-party image or appliance Image scanner, plus the vendor's SBOM if there is one "Scanned output, not built by us"
Supplier binary Ask for the supplier's build SBOM; binary analysis as a check Supplier, version, how it was obtained
Legacy product with no pipeline Scan the shipped artifact; plan a pipeline "Scanned output" and an exception with an end date

Mixed CI: GitHub Actions, GitLab and Jenkins in one organization

The policy is CI-agnostic by design: it defines the output, so a GitLab or Jenkins pipeline can meet it with a generator step, and a GitHub Actions pipeline can use build-time recording. Rules for a generator step:

  • Run it in the release job, after the install step, not in a separate job that installs again.
  • Pin the generator's version, and pin CI actions to a commit, not a tag. Why that matters for scanners in particular is covered in open source SBOM generators.
  • Record the generation context as "after install, from files", so readers know what it saw.
  • Validate the output against the same schema as every other pipeline.

Open source generators such as Syft, Trivy and cdxgen can all write CycloneDX and SPDX, so the format rule does not force a tool choice. To compare them, see SBOM tools compared.

CRACI today supports GitHub Actions; GitLab CI and Jenkins are on the roadmap, with no date. The rollout consequence: start build-time recording where GitHub Actions is used, keep generator steps elsewhere, and track both in the same coverage report.

When a customer wants a specific format version

A customer asks for CycloneDX 1.6, and your tooling writes 1.7. It happens because their ingestion tool or their own policy is pinned to a version; BSI's floor is 1.6, so EU customers often name it. Your options:

  1. Ask whether the newer version is accepted. Often it is.
  2. Convert with the CycloneDX CLI, then validate against the target version, and check what was lost: fields that exist only in the newer specification are dropped.
  3. For an SPDX request, the same CLI converts to and from SPDX JSON 2.3. Its documentation warns that converting between SPDX and CycloneDX "can result in the loss of some information".
cyclonedx convert --input-file bom.cdx.json --input-format json \
  --output-file bom-1.6.cdx.json --output-format json --output-version v1_6

cyclonedx validate --input-file bom-1.6.cdx.json --input-format json \
  --input-version v1_6 --fail-on-errors

The validator checks against CycloneDX 1.7 unless you pass --input-version. Policy rule: keep the original SBOM as the record. A converted copy is a derived delivery; store the derivation (tool, version, date) with it. More on answering these requests in customer SBOM requests, and on the two formats in CycloneDX vs SPDX.

License data for legal

The same SBOM can serve the legal team, if the policy asks for the right fields. Legal needs the license of every component, the difference between a declared license (what the package says) and a concluded one (what someone verified), copyleft obligations, and a list of components with no license. CycloneDX 1.6 marks licenses as declared or concluded, and SPDX has separate fields for both.

  • Require SPDX license identifiers or expressions, such as MIT or Apache-2.0 OR MIT.
  • Flag NOASSERTION and missing licenses for review. In SPDX, NOASSERTION means no determination was made or no information was given; it is not a license.
  • Route copyleft licenses to legal before release, not after.

An SBOM carries declared licenses. A license compliance review, with concluded licenses, notices and obligations, is legal work or a job for a specialist tool; see open source license compliance.

Rolling it out team by team

Phase Owner Exit criterion Typical blocker
1. Inventory: products, pipelines, CI systems and current generators, one row per release pipeline Product security Every shipped product has a row Nobody knows which pipeline builds a release
2. Publish the policy with the exception register Product security Policy approved and published Debate over which tool to mandate (the policy does not mandate one)
3. Pilot with one or two teams that have a reason: a customer request, a deadline, a product the current tool cannot handle Pilot teams Pilot releases ship with compliant SBOMs Teams without a burning reason deprioritize it
4. Add validation gates in the pilot pipelines Pilot teams An invalid SBOM fails the release Fear of blocking releases; start with a warning
5. Expand by CI system; retire a per-team scanner only when the replacement meets the policy Product teams Coverage target met per CI system Pipelines nobody owns
6. Report coverage monthly Product security The report goes to management every month Metrics computed by hand

The inventory spreadsheet in phase 1 should be the last one; moving off spreadsheets is covered in SBOM automation. A Yocto-based product is a common pilot, because general-purpose scanners struggle with it; see why SBOM scanners miss Yocto builds.

Measuring coverage

The numbers management and auditors ask for, each defined as a ratio:

  • Release coverage: releases shipped with a policy-compliant SBOM ÷ releases shipped.
  • Product coverage: products with at least one compliant SBOM in the current support period ÷ products in support.
  • Validation pass rate: SBOMs that pass validation ÷ SBOMs generated.
  • Identifier coverage: components with a package URL (or a CPE) ÷ components.
  • License coverage: components with a declared license ÷ components.
  • Completeness: SBOMs whose generator states they are complete ÷ SBOMs. Only possible where the tool reports it.
  • Time to SBOM: time from release to SBOM available.

A sample monthly report (illustrative figures):

Metric GitHub Actions pipelines GitLab and Jenkins pipelines All
Release coverage 46 / 48 19 / 27 65 / 75
Validation pass rate 100% 92% 97%
Identifier coverage 99% 88% 95%
Open exceptions 1 4 5

As evidence for a regulator or an auditor, the set is the policy, the exception register, the coverage report and a few sample SBOMs. It shows one process, applied and measured. For the CRA side, see the CRA guide.

How CRACI fits

For GitHub Actions pipelines, CRACI is the runner: you set runs-on: craci, and every job gets a recorded SBOM without a generator step. Each SBOM carries a completeness state, the transitive dependencies, a package URL per package and each component's declared license, and exports as CycloneDX or SPDX. License policy rules can block a build, monitored SBOMs are re-evaluated continuously, and build history shows which builds contained a vulnerable dependency. That gives those teams the policy's "generated in the release build", "identifier" and "license" rows by default. Third-party SBOMs can be added, so bought-in components are monitored alongside your builds.

See SBOM generation on CRACI and SBOMs in GitHub Actions.

When CRACI is not the answer

  • Teams on GitLab CI or Jenkins: not today, it is on the roadmap. Use a generator step that meets the policy.
  • GitHub Enterprise Server: not supported today.
  • Infrastructure and host scanning, meaning what is installed on servers and misconfigurations: a different job; keep those tools.
  • Binary or firmware analysis of supplier images you do not build: CRACI records builds and does not scan binaries.
  • Long retention: CRACI keeps build data for 180 days by default, with custom retention on Enterprise. If your policy needs SBOMs for longer, export them to your own store.

SBOM generation across teams: frequently asked questions

How do we standardize SBOM generation across teams that use different tools?

Standardize the output, not the tool. Write one policy that fixes the format and version, minimum fields, when the SBOM is generated, where it is stored, how long it is kept and who validates it. Let each pipeline meet it with the best generator it can run, and measure coverage against the policy.

What should an SBOM policy include?

Scope, format and minimum version, minimum fields (package URL, supplier, version, declared license, dependencies, tool, timestamp, generation context), generation in the release build, full transitive depth, storage with the release, retention, validation in the pipeline, ownership, and a time-boxed exception register.

Should every team run its own scanner, or should SBOMs come from the build?

From the build, where you can. A build-time SBOM describes the build that shipped and gives every team on that pipeline the same shape of SBOM. Keep scanners for what you did not build: third-party images, supplier binaries and legacy products with no pipeline.

A customer wants our SBOM in an older CycloneDX version. What do we do?

Ask first whether the newer version is accepted. If not, convert a copy with the CycloneDX CLI (cyclonedx convert with --output-version v1_6), validate it against that version, and check which fields were dropped. Keep the original as the record and store how the copy was derived.

How do we get license data to legal from our SBOMs?

Require SPDX license identifiers or expressions in the policy, flag NOASSERTION and missing licenses for review, and route copyleft licenses to legal before release. The SBOM carries declared licenses; concluded licenses, notices and obligations need a license review.

How do we measure SBOM coverage across products?

As ratios: releases shipped with a compliant SBOM, products in support with a compliant SBOM, validation pass rate, components with a package URL, components with a declared license, complete SBOMs where the tool reports it, and time from release to SBOM. Report them monthly by CI system.

What evidence do we need to show an auditor that SBOM generation is consistent?

The policy, the exception register, the monthly coverage report and a few sample SBOMs with their generation context. Together they show one process, applied to every release and measured, which is what an auditor or a customer checks.

Pilot it on one GitHub Actions pipeline

Book a demo and run one release pipeline on CRACI. Get an SBOM recorded from the build and check it against your policy.

Book a demo