Skip to content

The 24-hour reporting duty is in force. Check whether it applies to you

Why your container SBOM matches nothing

Declara4 min read
  • 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?

One email a month: how many vulnerabilities were added to the CISA exploited catalogue, and how many of them sit in components that appear in real inventories. Counts only. It is not a list, and nothing else is sent.

This produces evidence, timelines and drafts. It is not legal advice, and you remain the party responsible for reporting.