SBOM
An SBOM should describe the build that shipped
Most SBOMs are reconstructed after the fact from whatever files are lying around. Pointed at the same code, the common tools disagree. Here is why, and what recording the build changes.
Scanning a folder versus recording a build
Most SBOM generators are scanners. You point one at a source tree or a container image, and it works out what it thinks went in by reading manifests, lockfiles, installed packages or binaries. That is useful, and for software you did not build yourself it is often the only option.
The weakness is that a scanner only sees what is in front of it. If no lockfile was committed, if dependencies were not installed before the scan, or if the product ships as a compiled binary, the scanner has less to work with than the build did. A scan that finds no components at all still produces a valid SBOM document, which is the part that catches people out.
CRACI takes a different approach. It generates the SBOM inside its own CI runner, so the artifact shipped is the artifact described. It is not a scan that ran somewhere nearby.
SBOM basics: SPDX, CycloneDX and OpenVEX
What an SBOM records, how the SPDX and CycloneDX formats differ in practice, and what OpenVEX adds about whether a vulnerability actually affects a product.
Petteri Pulkkinen and Erika Marttinen at Kubernetes Community Days Helsinki 2026, 16:50 to 22:05 of SBOMbastic: Getting Ready for Upcoming EU Cybersecurity Regulation in Software Supply Chains.
What a scan depends on
Syft, Trivy, cdxgen, the Microsoft SBOM Tool, GitHub's dependency graph export and the Socket CLI all work out an SBOM from what they can read. The patterns below follow from how these tools are documented to work.
No component is found by every tool
Each generator decides for itself what counts as a component and which files to trust, so the same pinned commit gives each of them a different list.
Some of that is naming. Tools identify the same GitHub Action under different package URL types, so they can list the same thing and still not match. That is part of the problem too: an SBOM that uses a different identifier for a component is harder to match against vulnerability data.
Whether you installed first can matter more than the tool
Express 5.2.1 does not commit a lockfile. Pointed at a clean checkout, a
generator that resolves dependencies from lock files or an installed tree
has neither in front of it, so none of the npm dependencies declared in
package.json reach the document. It can still return a list,
made of whatever else it recognizes, such as GitHub Actions and the
workflow files that reference them. No tool here is broken.
On an installed copy of the same repository, one Syft flag changes the answer again. Same tool, same tree, same version. By design, a Syft directory scan reports what a project declares, and the cataloger that reads installed packages is not in its default set for directories.
Some software has no manifest to read
The anthropics/claude-code repository at v2.1.259 has no dependency
manifest of any kind: no package.json, lockfile or
equivalent. What each generator returns for it is assembled from other
file types, so the answer says more about the tool than about the project.
The published npm package is not much different. @anthropic-ai/claude-code declares zero runtime dependencies and
eight platform-specific packages that wrap a prebuilt binary.
@openai/codex 0.149.1 has the same shape. What runs on the
developer's machine is a compiled binary that no manifest reader can see
into. This is not a flaw in any one tool. It is a distribution model that
after-the-fact scanning cannot describe.
Versions and suppliers
Where two tools find the same package, they do not always agree on its version. One may pin a GitHub Action to a commit SHA while another reports a release tag. A vulnerability lookup keyed on the wrong version gives a confident wrong answer.
The supplier field, one of the NTIA minimum elements, often comes out empty, because most generators have no source for it.
What recording the build changes
CRACI runs your GitHub Actions jobs on its own runners. A package-aware proxy observes the traffic from each job to package sources, so the SBOM lists what the job actually pulled, including transitive dependencies, rather than what a manifest says it should need.
Caches are where build-time recording gets hard. A package restored from a cache never touches a package registry, so a proxy alone would miss it. CRACI carries dependency evidence with the cache, so packages restored from a cache stay in the SBOM of the job that restored them.
Detected ecosystems include npm, PyPI, RubyGems, Cargo, Go, Nix and OCI, plus operating system packages and source or download presets. JFrog Artifactory npm and PyPI mirrors work. Declared license metadata is included in the export. SBOMs export in CycloneDX and SPDX.
Completeness, stated per job
A recorded SBOM is only as good as the recording. Instead of presenting every SBOM as complete, CRACI states completeness for each job and each cache, in one of five states:
- Complete. CRACI observed complete job evidence, and every restored cache was complete.
- Complete with connections. The requirements passed, but the job used one or more custom network connections, so it is worth reviewing what came through them.
- Incomplete. CRACI identified a condition that could leave dependencies out of the SBOM.
- Unavailable. CRACI could not finish evaluating the evidence.
- Not recorded. The job predates completeness reporting.
Completeness is transitive across caches. If a job restores an incomplete cache, its SBOM is incomplete too, and any cache that job produces carries the incomplete status forward. You know which SBOMs you can rely on and which ones need a second look.
The hard parts: hooks, noise and staying current
Three problems the talk calls out: post-install hooks that fetch things no manifest lists, finding the dependencies that matter in a long list, and keeping the SBOM current, which means generating it in CI on every build.
Petteri Pulkkinen and Erika Marttinen at Kubernetes Community Days Helsinki 2026, 25:39 to 27:22 of SBOMbastic: Getting Ready for Upcoming EU Cybersecurity Regulation in Software Supply Chains.
Provenance: linking the SBOM to the artifact
An SBOM is more useful when you can prove which artifact it describes. CRACI produces signed evidence that links an artifact to the build that produced it, including OCI artifacts. Signatures use Ed25519 over SHA-512. Through the API, you can follow an artifact to its producing build, its CycloneDX SBOM and the job's network trace. CRACI does not claim a SLSA level.
Where a scanner is still the right tool
Build-time recording covers software you build. For a third-party image or binary you did not build, a scanner is still the practical choice, and the tools above are capable and widely used. The point is not that scanners are bad. It is that the SBOM for your own releases should come from the build that made them.
A CRACI SBOM also has limits of its own. It covers jobs that run on CRACI runners, which today means GitHub Actions on Linux.
Read more about SBOM generation in CRACI, or see how it fits CRA compliance work.
Compare an SBOM from your own build
Book a demo and run one of your workflows on CRACI. Put its SBOM next to the one your current tool produces and see where they differ.
Book a demo