You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tooling.apache.org by ar...@apache.org on 2026/07/24 09:25:30 UTC
(tooling-trusted-releases) 03/03: Fix primary component in generated SBOMs where we can do so
This is an automated email from the ASF dual-hosted git repository.
alitheg pushed a commit to branch arm
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-releases.git
commit 6e3c6ba92ab131fc83fb08311a059e85b124acf5
Author: Alastair McFarlane <ar...@apache.org>
AuthorDate: Fri Jul 24 10:17:48 2026 +0100
Fix primary component in generated SBOMs where we can do so
---
atr/tasks/sbom.py | 28 ++++++++++++++++++++
tests/unit/test_api_sbom_generate.py | 51 ++++++++++++++++++++++++++++++++++++
2 files changed, 79 insertions(+)
diff --git a/atr/tasks/sbom.py b/atr/tasks/sbom.py
index 3187342e..51fb3f06 100644
--- a/atr/tasks/sbom.py
+++ b/atr/tasks/sbom.py
@@ -531,6 +531,7 @@ async def _generate_cyclonedx_core(
try:
sbom_data = json.loads(stdout_str)
_strip_temp_prefix(sbom_data, str(temp_dir))
+ _promote_primary_component(sbom_data)
log.info(f"Successfully parsed syft output for {artifact_path}")
# Write the SBOM data to the specified output path
@@ -575,3 +576,30 @@ def _strip_temp_prefix(doc: dict[str, Any], temp_dir: str) -> None:
name = component.get("name")
if isinstance(name, str) and name.startswith(temp_dir):
component["name"] = name.removeprefix(temp_dir)
+
+
+def _promote_primary_component(doc: dict[str, Any]) -> None:
+ # Scanning an archive, syft describes it with a synthetic file component and catalogues the
+ # real package alongside the rest. Where that package is the sole root of the dependency graph
+ # it is the thing the SBOM is really about, so it stands in as the primary component, carrying
+ # its own identifier and rooting the graph on itself
+ metadata = doc.get("metadata")
+ if not (isinstance(metadata, dict) and isinstance(metadata.get("component"), dict)):
+ return
+ root_component = _dependency_graph_root(doc)
+ if root_component is None:
+ return
+ metadata["component"] = root_component
+ doc["components"] = [c for c in doc.get("components", []) if c is not root_component]
+
+
+def _dependency_graph_root(doc: dict[str, Any]) -> dict[str, Any] | None:
+ dependencies = doc.get("dependencies", [])
+ if not dependencies:
+ return None
+ depended_on = {ref for entry in dependencies for ref in entry.get("dependsOn", [])}
+ roots = [entry["ref"] for entry in dependencies if entry.get("ref") not in depended_on]
+ if len(roots) != 1:
+ # No single root means no unambiguous primary, so we leave syft's own choice in place
+ return None
+ return next((c for c in doc.get("components", []) if c.get("bom-ref") == roots[0]), None)
diff --git a/tests/unit/test_api_sbom_generate.py b/tests/unit/test_api_sbom_generate.py
index cd130395..b25e627a 100644
--- a/tests/unit/test_api_sbom_generate.py
+++ b/tests/unit/test_api_sbom_generate.py
@@ -136,6 +136,57 @@ def test_strip_temp_prefix_leaves_component_names_relative_to_the_scan_root() ->
assert doc["components"][1]["name"] == "plexus-utils"
+def _syft_shaped_doc() -> dict:
+ # syft describes the scanned archive with a synthetic file component and roots the dependency
+ # graph on the real package it found inside
+ return {
+ "metadata": {"component": {"bom-ref": "file-wrapper", "type": "file", "name": "widget-1.0.jar"}},
+ "components": [
+ {"bom-ref": "pkg:maven/org.example/widget@1.0", "type": "library", "name": "widget", "version": "1.0"},
+ {"bom-ref": "pkg:maven/org.example/helper@2.0", "type": "library", "name": "helper", "version": "2.0"},
+ ],
+ "dependencies": [
+ {"ref": "pkg:maven/org.example/widget@1.0", "dependsOn": ["pkg:maven/org.example/helper@2.0"]},
+ ],
+ }
+
+
+def test_promote_primary_component_replaces_the_file_wrapper_with_the_graph_root() -> None:
+ doc = _syft_shaped_doc()
+
+ sbom._promote_primary_component(doc)
+
+ # The primary is now the real package, not the synthetic file
+ assert doc["metadata"]["component"]["bom-ref"] == "pkg:maven/org.example/widget@1.0"
+ assert doc["metadata"]["component"]["type"] == "library"
+ # It no longer appears twice, and the graph still resolves against it
+ assert [c["bom-ref"] for c in doc["components"]] == ["pkg:maven/org.example/helper@2.0"]
+ assert doc["dependencies"][0]["ref"] == doc["metadata"]["component"]["bom-ref"]
+
+
+def test_promote_primary_component_abstains_without_a_single_root() -> None:
+ # A distribution that bundles sibling modules has no one primary, so the file wrapper stands
+ doc = _syft_shaped_doc()
+ doc["dependencies"] = [
+ {"ref": "pkg:maven/org.example/widget@1.0", "dependsOn": []},
+ {"ref": "pkg:maven/org.example/helper@2.0", "dependsOn": []},
+ ]
+
+ sbom._promote_primary_component(doc)
+
+ assert doc["metadata"]["component"]["bom-ref"] == "file-wrapper"
+ assert len(doc["components"]) == 2
+
+
+def test_promote_primary_component_abstains_without_a_dependency_graph() -> None:
+ doc = _syft_shaped_doc()
+ del doc["dependencies"]
+
+ sbom._promote_primary_component(doc)
+
+ assert doc["metadata"]["component"]["bom-ref"] == "file-wrapper"
+
+
def test_task_get_results_round_trip_typed_result() -> None:
task = sql.Task(task_type=sql.TaskType.SBOM_GENERATE, task_args={}, asf_uid="test")
task.result = results.SBOMGenerate(
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@tooling.apache.org
For additional commands, e-mail: commits-help@tooling.apache.org