From d757b82ebfca0280763dedb94beceb1efcbe1bfb Mon Sep 17 00:00:00 2001 From: Ruben Romero Montes Date: Tue, 22 Jul 2025 16:16:32 +0200 Subject: [PATCH] fix: allow missing root component purl Signed-off-by: Ruben Romero Montes --- .../sbom/cyclonedx/CycloneDxParser.java | 7 +++ .../sbom/cyclonedx/CycloneDxParserTest.java | 11 +++++ .../cyclonedx/no-root-purl-sbom.json | 44 +++++++++++++++++++ 3 files changed, 62 insertions(+) create mode 100644 src/test/resources/cyclonedx/no-root-purl-sbom.json diff --git a/src/main/java/com/redhat/exhort/integration/sbom/cyclonedx/CycloneDxParser.java b/src/main/java/com/redhat/exhort/integration/sbom/cyclonedx/CycloneDxParser.java index bf6d505c..e6160f24 100644 --- a/src/main/java/com/redhat/exhort/integration/sbom/cyclonedx/CycloneDxParser.java +++ b/src/main/java/com/redhat/exhort/integration/sbom/cyclonedx/CycloneDxParser.java @@ -76,6 +76,13 @@ public DependencyTree buildTree(InputStream input) { } else if (componentPurls.containsKey(rootComponent.get().getBomRef())) { rootRef = componentPurls.get(rootComponent.get().getBomRef()); } + if (rootRef == null) { + rootRef = + new PackageRef( + String.format( + "pkg:generic/%s@%s", + rootComponent.get().getName(), rootComponent.get().getVersion())); + } } var tree = treeBuilder.dependencies(buildDependencies(bom, componentPurls, rootRef)).build(); return tree; diff --git a/src/test/java/com/redhat/exhort/integration/backend/sbom/cyclonedx/CycloneDxParserTest.java b/src/test/java/com/redhat/exhort/integration/backend/sbom/cyclonedx/CycloneDxParserTest.java index 0ba7d470..dce52431 100644 --- a/src/test/java/com/redhat/exhort/integration/backend/sbom/cyclonedx/CycloneDxParserTest.java +++ b/src/test/java/com/redhat/exhort/integration/backend/sbom/cyclonedx/CycloneDxParserTest.java @@ -108,6 +108,17 @@ void testParseInvalidSbom() throws IOException { } } + @Test + void testParseSbomWithNoRootPurl() throws IOException { + CycloneDxParser parser = new CycloneDxParser(); + try (InputStream input = + getClass().getResourceAsStream(TEST_RESOURCES_PATH + "no-root-purl-sbom.json")) { + assertNotNull(input, "Test resource not found"); + var tree = parser.buildTree(input); + assertEquals(1, tree.dependencies().size()); + } + } + @ParameterizedTest @ValueSource(strings = {"1.0", "1.1", "1.2", "1.3", "1.4", "1.5", "1.6"}) void testSupportedVersions(String version) throws IOException { diff --git a/src/test/resources/cyclonedx/no-root-purl-sbom.json b/src/test/resources/cyclonedx/no-root-purl-sbom.json new file mode 100644 index 00000000..fded6050 --- /dev/null +++ b/src/test/resources/cyclonedx/no-root-purl-sbom.json @@ -0,0 +1,44 @@ +{ + "bomFormat": "CycloneDX", + "specVersion": "1.5", + "version": 1, + "metadata": { + "timestamp": "2025-07-22T12:00:00Z", + "tools": [ + { + "vendor": "OpenAI", + "name": "ChatGPT-SBOM-Generator", + "version": "1.0" + } + ], + "component": { + "type": "application", + "name": "example-app", + "version": "1.0.0", + "bom-ref": "pkg:generic/example-app@1.0.0" + } + }, + "components": [ + { + "type": "library", + "name": "commons-compress", + "version": "1.21.0.redhat-00001", + "publisher": "Red Hat, Inc.", + "group": "org.apache.commons", + "purl": "pkg:maven/org.apache.commons/commons-compress@1.21.0.redhat-00001?repository_url=https://maven.repository.redhat.com/ga/&type=jar", + "bom-ref": "pkg:maven/org.apache.commons/commons-compress@1.21.0.redhat-00001?repository_url=https://maven.repository.redhat.com/ga/&type=jar" + } + ], + "dependencies": [ + { + "ref": "pkg:generic/example-app@1.0.0", + "dependsOn": [ + "pkg:maven/org.apache.commons/commons-compress@1.21.0.redhat-00001?repository_url=https://maven.repository.redhat.com/ga/&type=jar" + ] + }, + { + "ref": "pkg:maven/org.apache.commons/commons-compress@1.21.0.redhat-00001?repository_url=https://maven.repository.redhat.com/ga/&type=jar", + "dependsOn": [] + } + ] +} \ No newline at end of file