Most teams that generate an SBOM have never read one, never queried one, and could not tell you where last month's is. The file is produced because something asked for it, stored somewhere, and never opened.
That is not an SBOM programme. It is a build artifact nobody consumes, and it is worth being clear about the difference, because the gap between "we generate SBOMs" and "SBOMs are useful to us" is where almost all the value sits.
What it actually is
A Software Bill of Materials is an inventory: every component in a piece of software, with versions, and ideally how they relate to each other. Two formats dominate — SPDX and CycloneDX — and for most purposes the choice matters less than being consistent.
# Generate one from a container image
syft registry.example.com/checkout:1.4.2 -o spdx-json > sbom.json
# What is actually in there
jq -r '.packages[] | "\(.name) \(.versionInfo)"' sbom.json | head
That is the whole idea. The interesting question is what you do with it.
The one question it answers well
"Are we affected, and where?"
When a vulnerability lands in a widely-used library, the expensive part is not patching. It is finding out which of your two hundred services include it, at which versions, and which are actually deployed.
This is the defence against dependency chain abuse actually being actionable: knowing a compromised package exists is not useful until you know whether you ship it.
Without an inventory that is an archaeology exercise across repositories, lockfiles and images, and it takes days. With one it is a query:
# Every image whose SBOM contains a given package
for f in sboms/*.json; do
jq -e --arg p "log4j-core" \
'.packages[] | select(.name == $p) | .versionInfo' "$f" >/dev/null 2>&1 \
&& echo "$(basename "$f"): $(jq -r --arg p "log4j-core" '.packages[] | select(.name==$p) | .versionInfo' "$f")"
done
Minutes, not days. That is the case for SBOMs, and it is a strong one — but notice it only works if you kept them, indexed them, and can tell which correspond to what is running right now.
What it does not do
This is where expectations go wrong, and inflated expectations are why the second year of an SBOM programme is usually quieter than the first.
It is not a vulnerability scan. An SBOM lists components. Matching those against advisories is a separate step with its own error rate, and the matching is harder than it sounds — package naming is inconsistent across ecosystems and advisory databases.
It does not tell you whether you are exploitable. A vulnerable component in your image says nothing about whether the vulnerable code path is reachable, or reachable from untrusted input. That judgement is the actual work, covered in turning 312 findings into four.
It does not prove anything about how the software was built. That is provenance and attestation — a different artifact answering a different question. An SBOM says what is inside; provenance says where it came from and who built it. The two are complementary and frequently confused, and the OWASP category for getting it wrong is improper artifact integrity validation.
It is not a security control. Nothing is prevented by generating one. It is an input to decisions, and only if somebody makes those decisions.
Its completeness is not guaranteed. Generated at build time from the package manager's view, it will miss things vendored by hand, downloaded by a build script, or statically linked. An SBOM that looks complete and is not is worse than an obviously partial one, because you will trust it during the incident it was meant to help with.
Where the generation point matters more than people expect
The same image produces materially different SBOMs depending on where in the pipeline you ask.
From source — accurate about direct and declared transitive dependencies, blind to whatever the base image contributes.
From the built image — sees the whole filesystem including OS packages, which is usually what you want for a container. This is the one to default to.
At runtime — sees what is actually loaded, which is the most accurate picture of exposure and the hardest to collect.
Most teams want the image-level SBOM. The common mistake is generating from source, missing every OS-level package in the base image, and believing the inventory is complete.
Making it actually useful
Four things, in order. The first two are where the value is and the ones most often skipped.
1. Store them somewhere you can query. Not an S3 bucket nobody has the path to. An SBOM you cannot find during an incident has no value, and the incident is the only time you need it quickly.
2. Attach them to the artifact. Store the SBOM alongside the image, addressed by digest so it cannot drift from what it describes. Container registries support this directly.
# Attach an SBOM to an image, addressed by digest
cosign attach sbom --sbom sbom.json registry.example.com/checkout@sha256:<digest>
Pinning by digest rather than tag is the part that matters. A tag moves; an SBOM describing whatever :latest meant last Tuesday is an inventory of nothing in particular.
3. Know which SBOM corresponds to what is running. This is the step that turns the inventory into an answer. If you cannot map a deployed workload to the digest it runs and from there to its SBOM, the query above tells you what you built, not what you are exposed to.
4. Then, and only then, wire it into scanning. Generating an SBOM and immediately pointing a scanner at it produces a large number of findings before you have the ability to triage them — which is how a team learns to ignore the output, and how you end up with a scan blocking the build on an advisory nobody has assessed.
Signing and verification
An SBOM that anyone can replace tells you what an attacker wants you to believe. Signing it makes tampering detectable.
This is the same machinery as image signing and it has the same failure mode: verification produces an error that cannot distinguish "unsigned" from "signed by an identity you did not expect", and the tempting fix is to disable the check. It is never the right fix.
The compliance question, honestly
A lot of SBOM adoption is driven by a requirement rather than a need, and it is worth naming that directly, because it changes what good looks like.
If you are generating SBOMs because a customer or regulator asks for one, the minimum viable version is genuinely small: generate at build, store durably, be able to produce it on request. That is legitimate and it is fine to stop there.
If you want the operational benefit — answering "are we affected" in minutes — you need the indexing and the deployed-artifact mapping, and those are the parts nobody asks you for and nobody funds.
Be clear with yourself about which you are building. A programme aimed at compliance that is measured as though it were aimed at incident response will look like a failure, and a programme aimed at incident response that stops at generation will be one.
Common mistakes
Generating and never querying. If nobody has ever run a query against your SBOM store, you do not know whether it works. Rehearse it: pick a package, find every affected service. Time it.
One SBOM per repository instead of per build. Dependencies change per build. An SBOM without a digest tied to a specific artifact describes nothing precisely.
Treating the count of components as a metric. Fewer components is often better, but the number is not a risk score and driving it down is not a security activity.
Assuming the format matters most. SPDX and CycloneDX are both fine. Consistency and retrievability matter enormously; the choice between them barely does.
Confusing it with provenance. They answer different questions and you likely want both, but adopting one does not give you the other.
The short version
An SBOM answers "are we affected, and where" quickly, which is worth a great deal during the week a widely-used library turns out to be vulnerable. It answers that only if you generate it from the built image, attach it to the artifact by digest, keep it somewhere queryable, and can map a running workload back to it.
Generating one and filing it away satisfies a requirement and buys nothing else. That is a reasonable thing to do on purpose, and an expensive thing to do by accident while believing otherwise.
Enjoyed this article?
Get more DevOps insights delivered to your inbox.
Get new posts by email
Subscribe to get an email when a new blog post is published. Skip anytime.
No spam, unsubscribe anytime.
Related Posts
Discussion
0 comments
Sign in to join the conversation.
Be the first to comment
Start a conversation about this post