Skip to content

SBOM: include version-managed and parent-pom Maven dependencies - #168

Open
villelaitila wants to merge 2 commits into
softagram:mainfrom
villelaitila:feature/sbom-versionless-managed-deps
Open

SBOM: include version-managed and parent-pom Maven dependencies#168
villelaitila wants to merge 2 commits into
softagram:mainfrom
villelaitila:feature/sbom-versionless-managed-deps

Conversation

@villelaitila

Copy link
Copy Markdown
Contributor

Problem

valid_for_bom required a version for a dependency to appear in the SBOM at all. A dependency whose version is governed by an imported BOM or an external parent pom has no version anywhere in the model, so it was dropped entirely. Modern Maven centralizes versions exactly there, which inverted SBOM quality: the newer the project, the emptier its SBOM.

Measured on a three-repo test project (spring-petclinic / junit4 / commons-lang):

repo components before components after pom ground truth
spring-petclinic (2025-style, Spring Boot parent) 3 30 28 deps + parent + devcontainer image
commons-lang (external commons-parent) 4 8 7 deps + parent
junit4 (2010-style, versions inline) 2 2 2 deps

External parent poms were also dropped even though their exact version is already in the model under parent_version (e.g. spring-boot-starter-parent 4.1.0).

Change

Commit 1 — include version-managed and parent-pom dependencies

  • valid_for_bom gains a coordinate branch: an element carrying maven coordinates and at least one incoming reference becomes a component with a versionless purl (pkg:maven/org.junit.jupiter/junit-jupiter). Versionless is canonical per purl spec and matches at package level — the same trade the existing ${...} unresolved-expression case already accepted. The incoming-reference requirement keeps out the husks that version-management redirection leaves behind.
  • extract_version falls back to parent_version, so external parents get full purls: pkg:maven/org.springframework.boot/spring-boot-starter-parent@4.1.0.
  • maven_purl treats an empty version like an unresolved expression instead of splicing a trailing @.

Commit 2 — harden against stored-model shapes (from adversarial review against real persisted models)

SBOMs are generated on demand from stored models with a multi-month lifetime, so the generator meets shapes the current analyzer no longer produces:

  • A parent_version-only clause (no coordinate requirement) was dropped: every model persisted before analyzers wrote coordinates onto parents has that shape, and admitting it spliced space-bearing element names into generic purls for all existing deployments. On coordinate-carrying models the coordinate branch already admits every parent, verified equivalent.
  • The coordinate branch requires is_maven_coordinate on both attributes: a versionless element with a ${} groupId builds no maven purl and has nothing spec-clean to emit.
  • A semicolon-joined parent_version (two poms naming one parent at different versions collide on one versionless element) yields a versionless purl; the raw value stays disclosed in the component version field instead of asserting a version that exists nowhere.
  • The generic fallback splice guards an empty version, closing the trailing-@ shape there too.

A cross-shape invariant test asserts no emitted purl or bom-ref in any fixture carries a space, a semicolon, or a trailing @.

Backward compatibility

Old generator vs this branch on identical models — strict superset, nothing lost, nothing malformed:

model shape 1.7.1 components this branch lost malformed
new (coordinates on parents) 10 40 0 0
old (parent_version only) 10 38 0 0

All outputs (single and --level multi) validate against the official CycloneDX 1.7 JSON schema.

Companion change

softagram-live's analyze_maven.py now writes groupId/artifactId onto parent references (and omits parent_version when the <parent> block has no version, instead of storing the string None). Ordering is safe in both directions: this branch handles both model generations, and the old generator ignores the new attributes.

Testing

  • 202 tests pass; the 9 new tests were each verified red against the prior behavior before implementation.
  • New fixture elements cover: BOM-managed versionless dep, unreferenced husk, coordinate-carrying parent, legacy parent (parent_version only), semicolon-joined parent version, ${} groupId.

A dependency whose version is governed by an imported BOM or an external
parent has no version attribute anywhere in the model, and valid_for_bom
dropped it entirely. Modern Maven centralizes versions in parents and
BOMs, so the newer the project, the emptier its SBOM: a Spring Boot
application lost its whole starter stack while a 2010-era pom came out
complete.

Three changes, one per missing shape:

- An element carrying maven coordinates and at least one incoming
  reference is now a component with a versionless purl. The incoming
  requirement keeps out the husks that version-management redirection
  leaves behind, whose references were re-pointed at versioned elements.
- extract_version falls back to parent_version, the attribute a <parent>
  block produces: the parent's exact version was already in the model
  and was discarded.
- maven_purl treats an empty version like an unresolved expression.
  Splicing it in would emit a trailing '@' - not a canonical versionless
  purl but a malformed versioned one.
Review against real stored models found four holes in the previous
commit, all in shapes the fresh-model happy path never meets:

- valid_for_bom admitted parent_version-only elements. Every model
  persisted before the analyzer wrote coordinates onto parents has that
  shape, and SBOMs are generated on demand from stored models, so the
  clause spliced space-bearing element names into generic purls for all
  existing deployments. Dropped: on coordinate-carrying models the
  coordinate branch already admits every parent.
- The coordinate branch accepted charset-rejected coordinates such as a
  ${} groupId resolved only in an external parent. Versionless, such an
  element builds no maven purl and the fallback splice emits the raw
  space-bearing name. The branch now requires is_maven_coordinate on
  both attributes.
- Two poms naming one parent at different versions collide on one
  versionless element and the attribute transfer joins the versions
  with a semicolon. maven_purl now omits such a version; the raw value
  stays disclosed in the component's version field.
- The generic fallback splice had no empty-version guard, emitting a
  trailing '@' for versionless elements that fall through maven_purl.

A cross-shape invariant test asserts no emitted purl or bom-ref in any
fixture carries a space, a semicolon, or a trailing '@'.
@softagram-bot

Copy link
Copy Markdown

Softagram Impact Report for pull/168 (head commit: c149074)

TL;DR Changed code files: 3 | Directly impacted code files: 1

⭐ Change Overview

Showing the changed files, dependency changes and the impact - click for full size
(Open in Softagram Desktop for full details)

[]

📄 Full report

Impact Report explained. Give feedback on this report to support@softagram.com

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.

2 participants