Skip to content

Record a container package under the ecosystem it belongs to - #42

Merged
haksungjang merged 1 commit into
mainfrom
fix/container-scan-purl-type
Aug 10, 2026
Merged

Record a container package under the ecosystem it belongs to#42
haksungjang merged 1 commit into
mainfrom
fix/container-scan-purl-type

Conversation

@haksungjang

Copy link
Copy Markdown
Contributor

Found while fixing the SBOM-side OS matching in #35, and tracked as parity-review row #39 — this one is our own defect rather than a gap against another tool.

What was wrong

_persist_trivy_report named every package in every image the same way:

purl = f"pkg:apk/{pkg_name}@{installed}"
component = _get_or_create_component(..., purl=f"pkg:apk/{pkg_name}", package_type="apk")

A Rocky image's rpms, a Debian image's debs, and pip inside a python image were all inventoried as Alpine packages. No finding was lost — this path creates its own components — so the counts were right and only the identity was wrong. That is why it survived: nothing failed, and the screens looked correct.

docs/reference/data-sources.md already promised pkg:apk/*, pkg:deb/* and pkg:rpm/* for the container pipeline. The documentation was right and the code was not, so no doc change was needed for the fix itself.

The fix

_component_identity reads the PURL Trivy attaches to each finding — every Result it emits carries PkgIdentifier.PURL, verified against real scans of rockylinux:9-minimal (pkg:rpm/rocky/...), python:3.12-slim (pkg:deb/debian/... plus pkg:pypi/pip) and the existing alpine:3.19 recording. The package type is derived from the PURL rather than assumed.

Qualifiers are dropped from what is stored. distro=rocky-9.3 would make the same package version a new component on every point release of the base image, and both the first-detected clock and carried triage are keyed on the component version — so a 9.3 → 9.4 rebuild would report the whole image as newly vulnerable.

A finding with no derivable identity is skipped and logged. Calling it apk is the thing this change exists to stop.

The tests were confirming the bug

Two integration tests hand-built pkg:apk/{name}@{version} as their expectation, so a wrong implementation and a wrong expectation agreed with each other. Their fixtures now carry the shape a real trivy image writes, and the SLA test derives the PURL from the report instead of rebuilding it.

New recordings: rocky-9-image-report.json and debian-python-image-report.json, trimmed to the six most-affected packages and three CVEs each (per-package density preserved), with Packages narrowed to the same six to keep the files reviewable. Every entry is otherwise the tool's own output.

Migration

Existing rows are left as stored — a past scan keeps the identity it recorded, which is what that scan saw. Re-scanning an rpm or deb image corrects its inventory but resets that project's triage: verdicts and the first-detected clock are keyed on the component, and the corrected package is a different component. Deployments that only scanned Alpine images are unaffected, since those values were already right. installation/upgrade.md states this in EN and KO.

A data migration was considered and rejected. Correcting the old rows is not a rename: pkg:apk/openssl is one Component row that an Alpine openssl and a Rocky openssl both hang off, so untangling them means reading scan_components.raw_data per version, moving rows between components, merging where a correct component already exists, and cleaning up what empties. That is a data reconstruction whose failure mode is silent mis-attribution — the same class of defect being fixed here.

Verification

  • Backend tests/unit tests/integration on a fresh database: 6658 passed. The two test_reset_demo_scope_db failures reproduce on main unchanged.
  • ruff and mypy clean over the whole tree; ko-style --all --fail-on S2 reports 0.

Container persistence hardcoded pkg:apk/{name}@{version} and package type
"apk" for every package in every image. A Rocky image's rpms, a Debian
image's debs and pip inside a python image were all inventoried as Alpine
packages. No finding was lost, so the counts were right and only the identity
was wrong — which is why it survived, and why data-sources.md already
documented the pkg:apk / pkg:deb / pkg:rpm split the code did not honour.

Read the PURL Trivy attaches to each finding, as the SBOM matcher now does,
and derive the package type from it. Qualifiers are dropped: distro=rocky-9.3
would make the same package a new component on every point release and reset
the first-detected clock for the whole image. A finding with no derivable
identity is skipped rather than called apk.

Existing rows are left as they were stored. Re-scanning an rpm or deb image
corrects its inventory but resets that project's triage, since verdicts are
keyed on the component; upgrade.md says so in both languages.
@haksungjang
haksungjang merged commit f5291ef into main Aug 10, 2026
25 checks passed
@haksungjang
haksungjang deleted the fix/container-scan-purl-type branch August 10, 2026 02:27
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