6.7.7 Generating SBOMs from Bazel Builds

recommended

An SBOM for a Bazel-built artifact is not simply a printout of every target below the release target. It is a component inventory whose scope must match the bytes you intend to ship. Bazel's configured graph is valuable evidence, but it records several relationships—build tools, source inputs, data, and runtime dependencies— that do not all mean “included in this artifact.” Conversely, packaging can add or transform content in ways that a generic graph walk cannot classify by itself.

Start with the release subject

Generate the inventory for the immutable subject established by 6.7.4 From Build to Release Candidate, not for a mutable target name or whatever happens to be in bazel-bin. Record the target, configuration and platform that produced the subject, then bind the resulting SBOM to the subject digest. This makes the question testable: does this document describe the composition of these exact bytes?

Before choosing an implementation, classify the evidence you need:

Evidence classWhat it can tell youWhy it is not enough alone
Declared dependencyA target participates in the configured graphParticipation does not prove that its content ships
Build tool or inputA compiler, generator, source, or configuration influenced an actionInfluence is different from runtime composition
Runtime dependencyA component is required when the product runsThe relationship may not reveal which files were packaged
Included contentBytes or files are present in the release subjectInspection may lose the package identity, version, or license attached upstream

The distinction between tools and included dependencies is a known weak point in graph-derived SBOMs. Bazel's supply-chain work found that unclassified dependency edges can produce an easy but low-quality inventory in which build tools appear as shipped components.1 Treat that as a schema-design problem in your rules and metadata, not as a formatter problem at the end.

Use the graph for identity and relationships

The configured graph is the natural place to collect metadata that rules already understand: package identity, version, license declarations, and the role of a dependency edge. Providers can carry that typed metadata from producing rules, and an aspect can traverse selected dependency edges without changing the original targets. 4.8.1 Aspects — Cross-Cutting Graph Traversal explains the traversal model.

The current bazel-contrib/supply-chain project makes this architecture concrete. Its package_metadata module carries identity such as package URLs and licenses. The gather_metadata aspect collects filtered metadata and relationship edges into TransitiveMetadataInfo. The sbom rule then serializes that provider as graph JSON and runs a classification action; separate rules render the graph and classifications as SPDX or CycloneDX. The collector has rule-aware filters: for example, its checked-in filter table excludes genrule tools, exec_tools, and toolchains rather than reporting them as ordinary components.2

That is useful evidence of the direction, not a blanket completeness guarantee. The same repository still warns that it is in active initial development, and its 2026 roadmap treats action-level accountability and an end-to-end complete Bazel SBOM as future milestones. Verify the exact module release and rule behavior before adopting the API, and report ruleset-specific traversal gaps. The predecessor, rules_license, remains useful for understanding the historical provider-and-aspect design, but active development moved to the successor; do not start a new integration from its legacy symbols.3

Graph collection is strongest when rule authors expose the semantic difference between, for example, a compiler used to create a binary and a library linked into that binary. If a ruleset exposes only an undifferentiated dependency closure, an SBOM generator cannot reliably reconstruct that intent afterward. Mark the resulting field or component set as incomplete rather than silently treating every traversed target as runtime content.

Calibrate the collector to the artifact boundary

The graph answers “what Bazel knew while constructing this target.” The packaging boundary answers “what entered the subject.” Join both views.

For a package or image, prefer metadata emitted by the rule that performs assembly: a manifest of copied files, layer inputs, bundled libraries, generated resources, or another mapping from package identity to included content. When the packaging rule cannot provide that mapping, inspect the final artifact and reconcile what you find with graph metadata. Inspection can prove that a file or archive member exists, but it may need graph or ecosystem metadata to recover a stable component name and version.

Current repositories illustrate three different boundaries:

IntegrationEvidence it actually ownsBoundary you must preserve
supply-chain tools/sbomClassified package metadata from selected configured-graph edgesGraph classification does not prove final byte composition, and filters are only as complete as their rule knowledge
Distroless private/pkgSPDX fragments emitted for resolved Debian packages and composed along known OCI base, tars, and images relationshipsThis is strong, artifact-specific evidence for Distroless' assembly rules, but private/ is implementation evidence, not a supported downstream API4
rules_oci attestation exampleAttachment of a supplied SPDX JSON predicate to a built imageIt transports an existing SBOM; it does not derive one from oci_image or from Bazel's graph5

The contrast matters. A generic graph collector can retain identities across many rulesets but may misclassify an edge. A packaging rule knows exactly which declared layers it assembled but may see one opaque application archive as a single input. Artifact inspection sees what was emitted, yet can lose the provider metadata that gave those bytes a package URL or license. A credible pipeline records which of these statements each collector can make and joins them; it does not call all three “SBOM generation.” Signing or attaching the finished statement is covered by 6.7.8 Signing and Verifying Artifacts; moving an accepted subject and its evidence to a registry is covered by 6.7.10 Publishing Artifacts Reliably.

This two-source design also catches opposite omissions:

  • a declared library may be analyzed but removed by linking, pruning, or packaging;
  • a generator may contribute produced bytes without being a runtime component;
  • a data file or vendored archive may be shipped even though generic dependency metadata does not identify it as a package;
  • a base image or preassembled bundle may contribute many components behind one Bazel target.

Package-manager lockfiles can supplement missing package identity and version data, but they describe resolution, not necessarily composition. The Bazel SBOM roadmap used lockfiles pragmatically when package annotations were incomplete, while still identifying tool-versus-included classification as unresolved.1 A lockfile entry therefore needs the same reconciliation: was it merely available to the build, or did its content enter this release subject?

Make completeness an explicit result

Do not reduce SBOM generation to “file produced” versus “command failed.” Report the coverage of each evidence source and any unresolved joins. A useful result distinguishes:

  • complete: every in-scope packaged component has an identity and relationship, and every graph-derived component claimed as shipped is supported by composition evidence;
  • incomplete: a known class of content or transitive metadata is absent;
  • unknown: evidence is unavailable, incompatible, or cannot be reconciled with the immutable subject.

The labels are local contract terms, not SBOM-standard fields. They prevent a validly formatted document from being mistaken for a complete inventory. Format selection, ecosystem-specific package semantics, and registry delivery remain downstream concerns; this layer must hand them bounded, traceable evidence rather than an unqualified claim.

Test graph and composition against each other

The runnable graph-versus-composition fixture keeps the mismatch concrete. Its //release:bundle genrule declares two source inputs and a generator tool, but the tool copies only the runtime payload and adds one generated catalog line:

//release:bundle configured closure
├── //runtime:runtime-library.txt             declared input, copied
├── //unused:declared-but-not-shipped.txt     declared input, omitted
└── //tools:catalog_generator.sh              tool, implementation omitted

bazel-bin/release/bundle.txt
├── generated=catalog-v1
└── component=runtime-library

The first verifier assertions prove that the tool and both inputs occur in cquery 'deps(//release:bundle)'. The artifact assertions then prove that the bundle contains the generated catalog and runtime payload, but neither the unused input's contents nor the generator's implementation marker. This fixture does not generate an SBOM. It is a regression test for the false premise that a configured dependency closure equals the shipped-component set.

An inventory built over this fixture should classify the runtime payload as shipped, account for the catalog as included generated content, and exclude the generator and unused input from the shipped-component set. Whether the catalog is its own component or a file within the application must come from producer or package metadata, not from its generated status.

Extend the minimal fixture with richer mismatches one at a time:

  1. remove package metadata from the transitive runtime library;
  2. add a packaged file that has no graph identity;
  3. make the graph collector traverse the tool edge;
  4. supply composition metadata for a different artifact digest; and
  5. omit a transitive data edge from collection.

Each case must either be reconciled by another evidence source or produce an explicit incomplete/unknown result. It must never emit the same unqualified “complete” SBOM. Together, these extensions test the claim that matters: the system can detect disagreement between declared relationships and shipped content, not merely serialize a dependency graph. Add your ruleset's metadata provider and package manifest to the verifier instead of treating its current label and content assertions as an SBOM.

key takeaway

A Bazel dependency graph is evidence for an SBOM, not automatically the SBOM. Separate declared dependencies, build tools and inputs, runtime relationships, and content actually included in the immutable release subject. Use providers and aspects to retain package identity and graph semantics; use packaging metadata or artifact inspection to prove composition.

Join those views by subject digest and test deliberate mismatches. A declared-but-not- shipped dependency, a shipped generated or vendored component, an accidental tool edge, or missing transitive metadata must produce a correct classification or a visible incomplete/unknown result—never a silently incomplete “complete” inventory.

Check your understanding · 4 questions

1.A Bazel aspect emits a valid SPDX document for an OCI image target, but one image layer is an opaque application tar and no packaging manifest or artifact inspection was collected. What is the strongest justified conclusion?

Select one answer

2.Which claims require composition evidence in addition to Bazel graph metadata? Select all that apply.

Select all that apply

3.Match each observation to the inventory treatment it supports:

Drag each answer onto the matching prompt, or click an answer and then click a prompt

Answers
A runtime library has typed graph metadata and a matching package-manifest entry
A known class of transitive data lacks package metadata
Composition metadata names a different artifact digest
A code generator is on a tool edge and its implementation is absent from the artifact

4.Classify these current integration boundaries:

Choose True or False for each sentence

The supply-chain collector's rule-aware tool filters improve classification but do not prove end-to-end artifact completeness.
The rules_oci attestation example derives an SBOM by inspecting every image layer before attaching it.
Distroless private SPDX rules are useful artifact-specific evidence but are not a supported downstream rules API.
A package-manager lockfile proves that every resolved package entered the release subject.
0 of 4 answered

Footnotes

  1. SBOMs via Bazel: Roadmap Update — tool-versus-included classification, incomplete annotations, graph traversal, and lockfiles as supplementary metadata 1 2

  2. supply-chain — Bazel package metadata and compliance rules — current package metadata, graph gathering, SBOM serializers, rule filters, and stabilization roadmap

  3. rules_license — legacy Bazel license metadata and SBOM rules — legacy manifest/SBOM examples and migration to the active successor

  4. Distroless — minimal runtime images built with Bazel — internal Debian SPDX fragments and OCI composition through rule-specific output groups

  5. rules_oci — daemonless OCI image rules for Bazel — OCI assembly boundaries and an attestation example that consumes a supplied SPDX predicate