Skip to content

Match distro packages an SBOM never labelled with an OS - #35

Merged
haksungjang merged 1 commit into
mainfrom
feat/os-context-synthesis
Aug 9, 2026
Merged

Match distro packages an SBOM never labelled with an OS#35
haksungjang merged 1 commit into
mainfrom
feat/os-context-synthesis

Conversation

@haksungjang

Copy link
Copy Markdown
Contributor

Closes the parity gap tracked as #23 (BomLens enrich-os-context.py).

What was broken

Trivy chooses a distro advisory database from an operating-system component in the document, not from the package PURLs. A supplier SBOM can list every rpm on an image, each PURL well formed, and report zero vulnerabilities because it never names the distribution. The gap note said our ingest path was "presumed" affected; it is. Reproduced locally against Trivy 0.71.2 with five CentOS 7 rpm PURLs: 0 findings without the component, 306 with it. SPDX uploads behave the same way (0 → 166), and there the OS package is identified by the SPDXRef-OperatingSystem id prefix — primaryPackagePurpose: OPERATING_SYSTEM alone is not read.

While recording the fixture a second defect surfaced. Trivy labels an os-pkgs Result with the distro (centos, alpine), which _build_purl's Type table cannot map, so every distro finding was skipped as trivy_finding_skipped_no_purl — 12 matched findings, 0 persisted. Adding distro tokens to that table would not help: the reconstruction drops the namespace the stored PURL carries (pkg:rpm/centos/openssl@…).

What this does

  • services/os_context.py — infers (distro, version) by voting over distro PURLs and inserts the component, for CycloneDX and SPDX-JSON.
  • tasks/_trivy_input.py — writes the enriched document to a transient per-scan path and hands Trivy that. Wired into all three callers (ingest, source scan, rematch) so a finding cannot appear on one run and vanish on the weekly rematch.
  • vulnerability_matching — falls back to the PURL Trivy attaches to each finding. Reconstruction stays first, so every ecosystem that already matched is byte-for-byte unaffected (pinned by a test where the two disagree).

The upload is never edited. It backs the conformance verdict, the signature bundle and the sbom_cyclonedx artifact, so the enriched copy lives only in the workspace the scan deletes.

Deliberate divergences from upstream

  • No version rewriting. Upstream reduces an existing OS component's version to its major (rocky 8.108). Measured here on centos 7.9.2009, rocky 8.10, alma 9.3 and redhat 8.9 — each matched identically with and without the minor, so a supplier's stated version is left alone. This module only ever adds a component that is missing.
  • Tag-Value SPDX is not rewritten. Stated as a limit in the user guide rather than attempted with text surgery.
  • Accuracy over coverage. An unrecognised distro, or a deb/apk PURL with no distro= qualifier, contributes no vote and the document is scanned as it arrived. A wrong distro produces findings against packages that were never affected, and those are indistinguishable from real ones downstream.

Verification

  • Reproduction and every claim above measured against a real Trivy, not asserted from upstream's notes.
  • The Trivy report fixture is a real recording of that run, trimmed to four findings per package (hardening rule 3 — three packages × four CVEs is the density rpm scanning actually produces).
  • Backend: 5481 unit passed, ruff and mypy clean over the whole tree.
  • Integration run three times on fresh databases. The two test_reset_demo_scope_db failures reproduce on main unchanged; the others that appeared were order-dependent and did not repeat.

Noted, not fixed here

tasks/scan_container.py has its own persist path that records every OS package as pkg:apk/{name}@{version}, so rpm and deb images get apk-typed components. No findings are lost (it creates the components itself), so it is out of scope for this change and tracked separately.

Trivy picks a distro advisory database from an operating-system component,
not from package PURLs, so a supplier SBOM listing every rpm on an image
reported zero vulnerabilities. Measured on Trivy 0.71.2: 0 findings without
the component, 306 with it; SPDX behaves the same (0 -> 166) and needs the
SPDXRef-OperatingSystem id prefix, not primaryPackagePurpose.

Infer the distro from the packages and scan an enriched copy — the upload
itself is never edited, since it backs the conformance verdict and the
signature bundle. A second defect surfaced alongside: os-pkgs results are
labelled with the distro, which no PURL reconstruction maps, so every distro
finding was dropped as "no purl". Fall back to the PURL Trivy attaches to the
finding, leaving reconstruction first so existing ecosystems are untouched.

Upstream's rewrite of an existing OS version down to its major release is
deliberately not ported: centos 7.9.2009, rocky 8.10, alma 9.3 and redhat 8.9
each matched identically with and without the minor.
@haksungjang
haksungjang merged commit fe9b4cf into main Aug 9, 2026
25 checks passed
@haksungjang
haksungjang deleted the feat/os-context-synthesis branch August 9, 2026 22:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant