Why your container SBOM matches nothing
- sbom
- matching
- purl
- containers
Generate a bill of materials for a container image and count the components. Unless you
are shipping a static binary on scratch, most of what comes back is not your
application's dependencies. It is the distribution: a few hundred deb, rpm or apk
packages that arrived with the base image. That is the largest category in the file, and
it is the category most matching pipelines quietly return nothing for.
The short answer
Two separate problems, and fixing either one alone still leaves you with nothing. Advisory feeds and bills of materials identify the same operating-system package differently, so the join never happens. And even once it does, operating-system version strings cannot be ordered by a general-purpose comparator, so the range check silently fails to fire.
purl
A package URL: a compact, ecosystem-aware identifier for a software package, as in
pkg:npm/lodash@4.17.21 or pkg:deb/debian/openssl@1.1.1n-0+deb11u5. Both CycloneDX
and SPDX carry them, and they are what makes a bill of materials machine-comparable at
all rather than a list of names.
Problem one: the identifier never joins
A component in your SBOM is pkg:deb/debian/openssl. The namespace — debian — says
which distribution shipped it. An advisory feed carries the same information somewhere
else entirely: OSV labels the advisory's ecosystem Debian:11 and then names the
package plainly, openssl.
So if your match key is the whole purl path, a feed name and a component name can never
be equal, for any operating-system package, ever. Not rarely. Never. The key has to drop
the namespace for exactly those package types and keep it for everything else, because
for npm and maven the namespace really is part of the name.
The cost of dropping it is worth stating plainly: a Debian advisory can now meet an Ubuntu component of the same name. We take that deliberately. The version comparison still has to agree before anything is raised, an uncertain result is surfaced rather than hidden, and every alert says it is a reason to assess rather than a finding. A false negative here means a customer is never told at all.
Problem two: the version string
Here is a real one: 1:1.1.1n-0+deb11u5. A leading epoch, an upstream version, a
distribution revision, a security-update suffix. Hand that to a general-purpose semantic
version comparator and it reads everything after the first numeric run as a pre-release
tail, which means it will happily tell you that a patched package is older than the
vulnerable one.
The tilde is where this gets genuinely dangerous. Every distribution uses ~ to express
a pre-release, and it sorts before the version it precedes — 1.0~rc1 is older than
1.0. Order it the intuitive way and you have inverted the comparison: a fixed version
reads as affected, and an affected one reads as fixed. That is not a missed alert, it is
a confidently wrong one in both directions.
Debian, RPM and Alpine each specify their own ordering, and none of them is the generic one. There is no shortcut here; you implement the three algorithms or you do not support the packages.
The rule underneath both
Never treat an unknown ecosystem as semver. Guessing gets you a comparison that returns an answer, and an answer is worse than an admission, because nothing downstream can tell the two apart. Where the ecosystem is not one we implement, the comparison falls back to exact string equality and the match is flagged as exact rather than range-based, so the interface can say which kind of certainty it is offering.
That flag is the part worth copying if you copy nothing else. Matching produces three outcomes, not two: affected, not affected, and cannot tell. Systems that collapse the third into the second look cleaner and are silently lossy, and the loss lands on the components you understand least.
How to check your own
Take an SBOM for one of your container images, count how many components have a purl
type of deb, rpm or apk, and then count how many findings your pipeline has ever
produced for any of them. If the first number is in the hundreds and the second is zero,
that is not a clean bill of health.
You can run the same file through the SBOM check — it is free, needs no account, keeps nothing, and it will tell you which components it could not compare rather than pretending it could.
Not sure whether this applies to you?
This produces evidence, timelines and drafts. It is not legal advice, and you remain the party responsible for reporting.