SBOM: fix five CycloneDX generator defects - #169
Merged
villelaitila merged 1 commit intoAug 5, 2026
Conversation
Fixing defects about how components are typed, versioned and
deduplicated. Each fix below was pre-registered with a falsifiable row delta
and verified against a 72-model corpus; a main-vs-final diff over that corpus
classified every behavioural difference, with zero unclassified.
G1 Container images were emitted as "type": "library", because the value was
hardcoded. Map the purl type to the CycloneDX component type instead, so
docker/oci yield "container".
G3 MSBuild property references such as $(Version) reached purls verbatim,
matching no package and silently misreporting the version. The existing
unresolved-version guard covered only Maven's ${...} and applied only
inside maven_purl. Widen it to both dialects behind one shared predicate
used for every type. Still fullmatch, deliberately: 1.0-$(Suffix) is partly
known and keeps its version.
G4 The internal __slash__ placeholder was decoded in names but never in
versions, so an npm specifier survived raw into the purl. Decode it in
extract_version so the disclosed version field is corrected too. 51 rows.
G5 Case-variant NuGet ids were emitted as two components, splitting any
vulnerability match across two identities. Deduplicate on a case-folded key
for the ecosystems whose purl type definitions declare identity
case-insensitive (nuget, pypi). The key only: the emitted purl keeps the
model's casing, since normalising it would change component identity.
G9 A binary committed into source control was typed nuget by extension
inference, asserting a public package that does not exist -- a
dependency-confusion-shaped false positive. Fall back to generic when the
element's name is the stem of a file referencing it, citing the referencing
file names so the decision is auditable. 16 rows.
villelaitila
force-pushed
the
feature/sbom-generator-defects-2a
branch
from
August 5, 2026 05:07
095ae3c to
a5dc72e
Compare
Softagram Impact Report for pull/169 (head commit: a5dc72e)TL;DR Changed code files: 3 | Directly impacted code files: 1⭐ Change Overview
⭐ Details of Dependency Changes
[] 📄 Full report
Impact Report explained. Give feedback on this report to support@softagram.com |
villelaitila
added a commit
to villelaitila/sgraph
that referenced
this pull request
Aug 5, 2026
fixes Rebasing this branch over the five-defect commit resolved both overlap sites textually but broke each side's semantics at one line: - extract_version: the parent_version fallback landed as a plain 'if' after the restructured elif chain, overriding an explicit version instead of remaining the last resort. Restore it as the final elif; it now also flows through the __slash__ decode, which is the correct combined behavior. - purl_for: the URL-shaped-version branch lost its versionless return when the empty-version guard was woven in, so a URL version recorded its versionSource property but still spliced '@https://...' into the purl. Restore the return. Both regressions were caught by the existing tests of the respective sides (test_an_explicit_version_outranks_parent_version, test_a_url_shaped_version_yields_a_versionless_purl_and_records_its_source).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Fixes five defects in the CycloneDX generator, identified in a review of 674 level-3 SBOMs (12 459 component rows, 5 650 distinct purls).
Each fix was pre-registered with a falsifiable row delta before implementation, landed as a separate green step, and verified independently. A
main-vs-final diff across 72 stored models classified every behavioural difference in the corpus:"type": "library"— the value was hardcoded. Now mapped from the purl type, sodocker/ociyieldcontainer.__slash__placeholder was decoded in names but never versions, so an npm specifier survived raw into the purl. Now decoded inextract_version, correcting the disclosed version field too.nugetby extension inference, asserting a public package that does not exist. Now falls back togenericwhen the element's name is the stem of a file referencing it.$(Version)references reached purls verbatim. The unresolved-version guard covered only Maven's${...}, and only insidemaven_purl; now widened to both dialects behind one shared predicate used for every type.Scope — stated honestly
Validated against real models: G1, G4's decode half, and G9, at or above the counts in the review. G1 was additionally applied to a real SBOM artifact generated in 2025-04 by an older generator (2 885 components, pre-
genericera): exactly its 7pkg:docker/rows retype, and the 15 legacy???rows map safely tolibrary.Fixture-only, unconfirmed against real data: G3, G5, and G4's URL half change zero rows in 72 stored models. This corpus lacks the .NET/MSBuild content the reviewed estate has, so the reported 21 MSBuild rows and 4 case-variant pairs cannot be reproduced here. The fixes are sound against the purl-spec reasoning but are guarded only by fixtures.
Design notes
{nuget, pypi}only. Their purl type definitions declare identity case-insensitive.npmis deliberately excluded —case_sensitive: true, with pre-2015 mixed-case packages grandfathered, soJSONStreamandjsonstreamare distinct identities.genericis excluded because its names are opaque. Both exclusions are guarded by control tests.generic, losing a real vulnerability match. Accepted because the branch is rarely reached — only 17 components across 72 models reach extension inference, and no.dllhas ever won — not because vendored binaries were measured to be rare; this corpus holds essentially no vendored .NET content. Every downgrade cites the referencing file names so the decision is auditable. The one real discriminating case available,aiortc(a genuine public PyPI package inferred from a.whl), is correctly spared.Not included
Percent-encoding of purl name/version segments — it rewrites ~18 % of purls and changes component identity for any downstream system already storing the unencoded form, so it needs coordinating with ingest rather than riding along here.
Interaction with #168
Independent of #168 and based on
main. Both touchmaven_purl's guard — #168 extends its condition, this refactors it into a shared predicate — so whichever merges second resolves a one-line conflict.Verification
211 passed(193 pre-existing + 18 new). flake8 clean apart from two pre-existing E501s. Fixtures were mutation-tested: fold-set widening and narrowing,fullmatch→search, dropping the$(...)dialect, removing the decode, droppingversionSource, and.lower()→.casefold()are all caught by controls.