Skip to content

fix(sbom): keep every detected component version in CycloneDX and SPDX - #228

Merged
agustingroh merged 5 commits into
mainfrom
fix/sbom-multi-version-components
Oct 20, 2026
Merged

agustingroh merged 5 commits into
mainfrom
fix/sbom-multi-version-components

Conversation

@agustingroh

@agustingroh agustingroh commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #226
Fixes #229

Problem

The CycloneDX and SPDX Lite converters keyed components by the versionless purl and skipped any purl already stored. When files matched several releases of the same component, only the first version encountered survived, while the default JSON output kept them all. Which version won depended on result order, so separate scans of the same code could report different versions. In CycloneDX, vulnerabilities from every version were also attached to the single surviving component.

Changes

  • Shared helper (utils/purl.py): versioned_purl(purl, version) pins a purl to the detected version using PackageURL. Purls that already carry a version, entries without a version, and unparsable purls are returned unchanged, and the version is placed before any qualifiers. Both converters use it, so CycloneDX and SPDX always produce the same versioned purl.
  • CycloneDX (cyclonedx.py): components are keyed by the versioned purl, which is also the bom-ref. The component purl field is unchanged (versionless). Vulnerability affects refs use the same key, so each CVE points at the version it was reported on.
  • SPDX Lite (spdxlite.py): summaries are keyed by the versioned purl, so each version becomes its own package. This also fixes two existing edge cases in the package purl: a purl that already had a version was pinned twice (pkg:npm/lodash%404.17.21@4.17.21), and a missing version produced pkg:npm/lodash@None. SPDX IDs are unchanged for normal input.
  • Folder hash (scanner_hfh.py): append_vulnerabilities now receives the component's bom-ref (parameter renamed purl → bom_ref), otherwise the appended vulnerabilities would reference a component that no longer exists.
  • Duplicate matches at the same version still collapse into one entry, keeping the first match.
  • tests/test_spdxlite.py: tests/data/result.json contains scanner.c at 1.3.3 and 1.1.4 and engine at 5.4.0 and 4.0.4, so the expected package count goes from 6 to 8.

Behaviour change: CycloneDX bom-ref is now purl@version for every component, not only multi-version ones. Noted in the CHANGELOG.

Verification

  • Unit tests (python -m unittest): 344 run, OK, 1 skipped.
    • tests/test_sbom_multi_version.py: one entry per version in both formats, unique bom-refs and SPDX IDs, vulnerabilities referencing their own version, CycloneDX schema validation, the purl request built from a CycloneDX file, and both formats producing the same purls for edge cases.
    • tests/test_purl.py: versioned_purl with versionless, already-versioned, encoded and qualified purls, missing, numeric and commit-hash versions, and unparsable input.
  • Lint: make lint is clean on the changed files apart from scanner_hfh.py PLR0917, which is already present on main.
  • Real scan with 118 components, six of which match more than one release. Converting the same scan result:
Check main (1.54.2) This branch
CycloneDX components / SPDX packages 118 / 118 128 / 128
pkg:github/signalwire/freeswitch v1.6.7 only v1.6.7, v1.6.8
pkg:github/begeekmyfriend/yasea 1 version 5 versions
pkg:github/lllllt/multipicturelivewallpaper 1 version 3 versions
pkg:github/google/ringdroid, danielhk/android_frameworks_av_mtk, uizaio/uiza-android-sdk-player 1 version each 2 versions each
Every version in the JSON present in CycloneDX FAIL PASS
Every version in the JSON present in SPDX FAIL PASS
bom-refs / SPDX IDs unique PASS PASS
Vulnerability affects refs resolve to a component PASS PASS
SPDX documentDescribes matches packages PASS PASS
CycloneDX schema validation PASS PASS
  • A fresh scan --wfp run with -f cyclonedx and -f spdxlite keeps every multi-version component in both SBOMs. Separate scans can still differ slightly when the server returns a different match for a file between runs (for example a short versus a full commit hash), but each SBOM now reflects all versions in its own scan.

Container build (#229)

Separate commit, unrelated to the SBOM change: the container image stopped building because scancode-toolkit-mini now pulls pluggy 1.7.0, which plugincode 32.0.0 cannot register hooks with. The Dockerfile pins pluggy<1.7 next to the existing click workaround. Reproduced in a clean environment: importing plugincode.pre_scan fails with pluggy 1.7.0 and works with 1.6.0.

Release

Bumps the version to 1.54.3 and moves the CHANGELOG entries under [1.54.3].

Notes

@github-actions

Copy link
Copy Markdown

SCANOSS SCAN Completed 🚀

  • Detected components: 2
  • Undeclared components: 0
  • Declared components: 2
  • Detected files: 114
  • Detected files undeclared: 0
  • Detected files declared: 114
  • Licenses detected: 1
  • Licenses detected with copyleft: 0
  • Policies: ✅ 1 pass (1 total)

View more details on SCANOSS Action Summary

@github-actions

Copy link
Copy Markdown

SCANOSS SCAN Completed 🚀

  • Detected components: 2
  • Undeclared components: 0
  • Declared components: 2
  • Detected files: 113
  • Detected files undeclared: 0
  • Detected files declared: 113
  • Licenses detected: 1
  • Licenses detected with copyleft: 0
  • Policies: ✅ 1 pass (1 total)

View more details on SCANOSS Action Summary

@github-actions

Copy link
Copy Markdown

SCANOSS SCAN Completed 🚀

  • Detected components: 2
  • Undeclared components: 0
  • Declared components: 2
  • Detected files: 113
  • Detected files undeclared: 0
  • Detected files declared: 113
  • Licenses detected: 1
  • Licenses detected with copyleft: 0
  • Policies: ✅ 1 pass (1 total)

View more details on SCANOSS Action Summary

@agustingroh
agustingroh merged commit d6fc3eb into main Oct 20, 2026
5 checks passed
@agustingroh
agustingroh deleted the fix/sbom-multi-version-components branch October 20, 2026 15:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant