From 513b05871909dec782820e93b89e471cc6ce9d68 Mon Sep 17 00:00:00 2001 From: "omid.tavakoli" Date: Fri, 14 Nov 2025 10:39:10 +0100 Subject: [PATCH 1/8] sbom serialNumber validation handled --- plugins/dependency-checker/pom.xml | 12 ++ .../nodejs/NodeJsDependencyGenerator.java | 28 +++++ .../nodejs/NodeJsDependencyGeneratorTest.java | 112 ++++++++++++++++++ sauron-service/docker-compose.yml | 2 +- 4 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 plugins/dependency-checker/src/test/java/com/freenow/sauron/plugins/generator/nodejs/NodeJsDependencyGeneratorTest.java diff --git a/plugins/dependency-checker/pom.xml b/plugins/dependency-checker/pom.xml index 96d8c3b6..9fcd5f62 100644 --- a/plugins/dependency-checker/pom.xml +++ b/plugins/dependency-checker/pom.xml @@ -196,6 +196,18 @@ + + org.junit.jupiter + junit-jupiter-api + 5.12.2 + test + + + org.mockito + mockito-core + 5.17.0 + test + diff --git a/plugins/dependency-checker/src/main/java/com/freenow/sauron/plugins/generator/nodejs/NodeJsDependencyGenerator.java b/plugins/dependency-checker/src/main/java/com/freenow/sauron/plugins/generator/nodejs/NodeJsDependencyGenerator.java index 5509a86f..50f1c0e4 100644 --- a/plugins/dependency-checker/src/main/java/com/freenow/sauron/plugins/generator/nodejs/NodeJsDependencyGenerator.java +++ b/plugins/dependency-checker/src/main/java/com/freenow/sauron/plugins/generator/nodejs/NodeJsDependencyGenerator.java @@ -11,6 +11,7 @@ import java.nio.file.Path; import java.util.Arrays; import java.util.Map; +import java.util.UUID; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -110,6 +111,33 @@ private Path buildCycloneDxBom(Path repositoryPath) throws IOException, Interrup .outputFile(bomJson) .build() .run(); + + /* + * The npm sbom command can generate a BOM with an invalid serialNumber (e.g., containing '***'). + * This fails validation in DependencyTrack, which expects a valid UUID matching the pattern: + * ^urn:uuid:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ + * + * See DependencyTrack's validator: + * https://github.com/DependencyTrack/dependency-track/blob/master/src/main/java/org/dependencytrack/parser/cyclonedx/CycloneDxValidator.java + * + * To prevent validation failure, this code replaces the invalid part of the serialNumber + * with a valid, randomly generated UUID. + */ + + try (Stream lines = Files.lines(bomJson)) + { + String content = lines + .map(line -> { + if (line.trim().startsWith("\"serialNumber\":") && line.contains("***")) { + log.debug("Found invalid serialNumber in bom.json for project at {}. Replacing it with a valid UUID.", repositoryPath); + return line.replace("***", UUID.randomUUID().toString()); + } + return line; + }) + .collect(Collectors.joining(System.lineSeparator())); + Files.write(bomJson, content.getBytes()); + } + return bomJson; } diff --git a/plugins/dependency-checker/src/test/java/com/freenow/sauron/plugins/generator/nodejs/NodeJsDependencyGeneratorTest.java b/plugins/dependency-checker/src/test/java/com/freenow/sauron/plugins/generator/nodejs/NodeJsDependencyGeneratorTest.java new file mode 100644 index 00000000..6f2360cd --- /dev/null +++ b/plugins/dependency-checker/src/test/java/com/freenow/sauron/plugins/generator/nodejs/NodeJsDependencyGeneratorTest.java @@ -0,0 +1,112 @@ +package com.freenow.sauron.plugins.generator.nodejs; + +import com.freenow.sauron.plugins.command.Command; +import com.freenow.sauron.plugins.command.NonZeroExitCodeException; +import com.freenow.sauron.properties.PluginsConfigurationProperties; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.regex.Pattern; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; + +class NodeJsDependencyGeneratorTest +{ + @TempDir + Path tempDir; + + private MockedStatic commandMockedStatic; + private Command command; + + @BeforeEach + void setup() throws IOException + { + Files.createFile(tempDir.resolve("package-lock.json")); + + commandMockedStatic = Mockito.mockStatic(Command.class); + Command.CommandBuilder commandBuilder = Mockito.mock(Command.CommandBuilder.class); + command = Mockito.mock(Command.class); + + commandMockedStatic.when(Command::builder).thenReturn(commandBuilder); + + Mockito.when(commandBuilder.commandTimeout(any())).thenReturn(commandBuilder); + Mockito.when(commandBuilder.repositoryPath(any())).thenReturn(commandBuilder); + Mockito.when(commandBuilder.commandline(any())).thenReturn(commandBuilder); + Mockito.when(commandBuilder.outputFile(any())).thenReturn(commandBuilder); + Mockito.when(commandBuilder.build()).thenReturn(command); + } + + @AfterEach + void tearDown() + { + commandMockedStatic.close(); + } + + @Test + void testGenerateCycloneDxBomWithInvalidSerialNumberIsFixed() throws IOException, InterruptedException, NonZeroExitCodeException + { + // Given + Mockito.doAnswer(invocation -> { + Path bomJson = tempDir.resolve("bom.json"); + String bomContent = "{\n" + + " \"bomFormat\": \"CycloneDX\",\n" + + " \"specVersion\": \"1.4\",\n" + + " \"serialNumber\": \"urn:uuid:***\",\n" + + " \"version\": 1,\n" + + " \"components\": []\n" + + "}"; + Files.write(bomJson, bomContent.getBytes()); + return null; + }).when(command).run(); + + PluginsConfigurationProperties properties = new PluginsConfigurationProperties(); + NodeJsDependencyGenerator generator = new NodeJsDependencyGenerator(properties); + + // When + Path cycloneDxBom = generator.generateCycloneDxBom(tempDir); + + // Then + assertTrue(Files.exists(cycloneDxBom)); + String bomContent = new String(Files.readAllBytes(cycloneDxBom)); + assertFalse(bomContent.contains("***")); + assertTrue(Pattern.compile("\"serialNumber\": \"urn:uuid:[a-f0-9\\-]{36}\"").matcher(bomContent).find()); + } + + @Test + void testGenerateCycloneDxBomWithValidSerialNumberIsNotModified() throws IOException, InterruptedException, NonZeroExitCodeException + { + // Given + String validSerialNumberLine = "\"serialNumber\": \"urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79\""; + Mockito.doAnswer(invocation -> { + Path bomJson = tempDir.resolve("bom.json"); + String bomContent = "{\n" + + " \"bomFormat\": \"CycloneDX\",\n" + + " \"specVersion\": \"1.4\",\n" + + " " + validSerialNumberLine + ",\n" + + " \"version\": 1,\n" + + " \"components\": []\n" + + "}"; + Files.write(bomJson, bomContent.getBytes()); + return null; + }).when(command).run(); + + PluginsConfigurationProperties properties = new PluginsConfigurationProperties(); + NodeJsDependencyGenerator generator = new NodeJsDependencyGenerator(properties); + + // When + Path cycloneDxBom = generator.generateCycloneDxBom(tempDir); + + // Then + assertTrue(Files.exists(cycloneDxBom)); + String bomContent = new String(Files.readAllBytes(cycloneDxBom)); + assertTrue(bomContent.contains(validSerialNumberLine)); + } +} diff --git a/sauron-service/docker-compose.yml b/sauron-service/docker-compose.yml index f7a2786d..a683f270 100644 --- a/sauron-service/docker-compose.yml +++ b/sauron-service/docker-compose.yml @@ -70,4 +70,4 @@ services: target: /root/.ssh read_only: true ports: - - "8080:8080" \ No newline at end of file + - "8080:8080" From 27c727c2a3b4e47488a2a8bfdda603670e61e905 Mon Sep 17 00:00:00 2001 From: "omid.tavakoli" Date: Fri, 14 Nov 2025 12:20:00 +0100 Subject: [PATCH 2/8] used objectMapper to modify Json --- .../sauron/plugins/DependencyChecker.java | 24 ++++++++++++++--- .../nodejs/NodeJsDependencyGenerator.java | 27 ------------------- 2 files changed, 21 insertions(+), 30 deletions(-) diff --git a/plugins/dependency-checker/src/main/java/com/freenow/sauron/plugins/DependencyChecker.java b/plugins/dependency-checker/src/main/java/com/freenow/sauron/plugins/DependencyChecker.java index 9f706af8..a54b74c4 100644 --- a/plugins/dependency-checker/src/main/java/com/freenow/sauron/plugins/DependencyChecker.java +++ b/plugins/dependency-checker/src/main/java/com/freenow/sauron/plugins/DependencyChecker.java @@ -1,7 +1,9 @@ package com.freenow.sauron.plugins; import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.dataformat.xml.XmlMapper; import com.fasterxml.jackson.module.jaxb.JaxbAnnotationModule; import com.freenow.sauron.model.DataSet; @@ -21,6 +23,7 @@ import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.UUID; @Extension @Slf4j @@ -87,8 +90,23 @@ private List parseCycloneDxXml(Path bom) throws IOException private List parseCycloneDxJson(Path bom) throws IOException { - ObjectMapper oMapper = new ObjectMapper(); - var bomContent = oMapper.readValue(bom.toFile(), Bom.class); - return Optional.ofNullable(bomContent.getComponents()).orElse(Collections.emptyList()); + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode bomNode = objectMapper.readTree(bom.toFile()); + + /* + * The npm BOM generator may produce an invalid serialNumber, which can cause validation issues in DependencyTrack. + * https://github.com/DependencyTrack/dependency-track/blob/fa1eb0bb4c1ecf87d231a21e077055acb6b8b59d/src/main/java/org/dependencytrack/parser/cyclonedx/CycloneDxValidator.java#L90 + * which returns error like this + * {"status":400,"title":"The uploaded BOM is invalid","detail":"Schema validation failed","errors":["$.serialNumber: does not match the regex pattern ^urn:uuid:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"]} + * This code replaces the invalid serialNumber with a valid v4 UUID to ensure compatibility. + */ + if (bomNode.has("serialNumber") && bomNode.get("serialNumber").asText().contains("***")) + { + log.debug("Replacing invalid serialNumber in {} for project: {}", bom.getFileName(), bom.getParent()); + ((ObjectNode) bomNode).put("serialNumber", "urn:uuid:" + UUID.randomUUID()); + } + + Bom bomObject = objectMapper.treeToValue(bomNode, Bom.class); + return Optional.ofNullable(bomObject.getComponents()).orElse(Collections.emptyList()); } } diff --git a/plugins/dependency-checker/src/main/java/com/freenow/sauron/plugins/generator/nodejs/NodeJsDependencyGenerator.java b/plugins/dependency-checker/src/main/java/com/freenow/sauron/plugins/generator/nodejs/NodeJsDependencyGenerator.java index 50f1c0e4..2f33561e 100644 --- a/plugins/dependency-checker/src/main/java/com/freenow/sauron/plugins/generator/nodejs/NodeJsDependencyGenerator.java +++ b/plugins/dependency-checker/src/main/java/com/freenow/sauron/plugins/generator/nodejs/NodeJsDependencyGenerator.java @@ -11,7 +11,6 @@ import java.nio.file.Path; import java.util.Arrays; import java.util.Map; -import java.util.UUID; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -112,32 +111,6 @@ private Path buildCycloneDxBom(Path repositoryPath) throws IOException, Interrup .build() .run(); - /* - * The npm sbom command can generate a BOM with an invalid serialNumber (e.g., containing '***'). - * This fails validation in DependencyTrack, which expects a valid UUID matching the pattern: - * ^urn:uuid:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ - * - * See DependencyTrack's validator: - * https://github.com/DependencyTrack/dependency-track/blob/master/src/main/java/org/dependencytrack/parser/cyclonedx/CycloneDxValidator.java - * - * To prevent validation failure, this code replaces the invalid part of the serialNumber - * with a valid, randomly generated UUID. - */ - - try (Stream lines = Files.lines(bomJson)) - { - String content = lines - .map(line -> { - if (line.trim().startsWith("\"serialNumber\":") && line.contains("***")) { - log.debug("Found invalid serialNumber in bom.json for project at {}. Replacing it with a valid UUID.", repositoryPath); - return line.replace("***", UUID.randomUUID().toString()); - } - return line; - }) - .collect(Collectors.joining(System.lineSeparator())); - Files.write(bomJson, content.getBytes()); - } - return bomJson; } From 06ec006895f413e96ce5332da01a6629e2ce24fa Mon Sep 17 00:00:00 2001 From: "omid.tavakoli" Date: Fri, 14 Nov 2025 13:07:49 +0100 Subject: [PATCH 3/8] used objectMapper to modify Json --- .../sauron/plugins/DependencyChecker.java | 7 +- .../sauron/plugins/DependencyCheckerTest.java | 43 +++++++ .../nodejs/NodeJsDependencyGeneratorTest.java | 112 ------------------ 3 files changed, 47 insertions(+), 115 deletions(-) delete mode 100644 plugins/dependency-checker/src/test/java/com/freenow/sauron/plugins/generator/nodejs/NodeJsDependencyGeneratorTest.java diff --git a/plugins/dependency-checker/src/main/java/com/freenow/sauron/plugins/DependencyChecker.java b/plugins/dependency-checker/src/main/java/com/freenow/sauron/plugins/DependencyChecker.java index a54b74c4..cd9e7352 100644 --- a/plugins/dependency-checker/src/main/java/com/freenow/sauron/plugins/DependencyChecker.java +++ b/plugins/dependency-checker/src/main/java/com/freenow/sauron/plugins/DependencyChecker.java @@ -90,8 +90,8 @@ private List parseCycloneDxXml(Path bom) throws IOException private List parseCycloneDxJson(Path bom) throws IOException { - ObjectMapper objectMapper = new ObjectMapper(); - JsonNode bomNode = objectMapper.readTree(bom.toFile()); + ObjectMapper oMapper = new ObjectMapper(); + JsonNode bomNode = oMapper.readTree(bom.toFile()); /* * The npm BOM generator may produce an invalid serialNumber, which can cause validation issues in DependencyTrack. @@ -106,7 +106,8 @@ private List parseCycloneDxJson(Path bom) throws IOException ((ObjectNode) bomNode).put("serialNumber", "urn:uuid:" + UUID.randomUUID()); } - Bom bomObject = objectMapper.treeToValue(bomNode, Bom.class); + Bom bomObject = oMapper.treeToValue(bomNode, Bom.class); + return Optional.ofNullable(bomObject.getComponents()).orElse(Collections.emptyList()); } } diff --git a/plugins/dependency-checker/src/test/java/com/freenow/sauron/plugins/DependencyCheckerTest.java b/plugins/dependency-checker/src/test/java/com/freenow/sauron/plugins/DependencyCheckerTest.java index 86b49450..e7e5daeb 100644 --- a/plugins/dependency-checker/src/test/java/com/freenow/sauron/plugins/DependencyCheckerTest.java +++ b/plugins/dependency-checker/src/test/java/com/freenow/sauron/plugins/DependencyCheckerTest.java @@ -3,6 +3,7 @@ import com.freenow.sauron.model.DataSet; import com.freenow.sauron.properties.PluginsConfigurationProperties; import com.github.tomakehurst.wiremock.junit.WireMockRule; +import java.util.regex.Pattern; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -221,6 +222,48 @@ public void testDependencyCheckerNodeJs() throws IOException, URISyntaxException ); } + @Test + public void testSerialNumberSanitization() throws IOException, URISyntaxException + { + // Test with invalid serial number + DataSet dataSet = createDataSet(Map.of( + "package.json", "package.json", + "package-lock.json", "package-lock.json" + )); + String invalidBomContent = "{\n" + + " \"bomFormat\": \"CycloneDX\",\n" + + " \"specVersion\": \"1.4\",\n" + + " \"serialNumber\": \"urn:uuid:***\",\n" + + " \"version\": 1,\n" + + " \"components\": []\n" + + "}"; + Path bomJson = tempFolder.getRoot().toPath().resolve("bom.json"); + Files.write(bomJson, invalidBomContent.getBytes()); + dataSet.setAdditionalInformation("cycloneDxBomPath", bomJson.toString()); + + plugin.apply(createNodeJsPluginConfigurationProperties(), dataSet); + + String sanitizedBomContent = new String(Files.readAllBytes(bomJson)); + assertFalse(sanitizedBomContent.contains("***")); + assertTrue(Pattern.compile("\"serialNumber\": \"urn:uuid:[a-f0-9\\-]{36}\"").matcher(sanitizedBomContent).find()); + + // Test with valid serial number which should be kept unchanged + String validBomContent = "{\n" + + " \"bomFormat\": \"CycloneDX\",\n" + + " \"specVersion\": \"1.4\",\n" + + " \"serialNumber\": \"urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79\",\n" + + " \"version\": 1,\n" + + " \"components\": []\n" + + "}"; + Files.write(bomJson, validBomContent.getBytes()); + dataSet.setAdditionalInformation("cycloneDxBomPath", bomJson.toString()); + + plugin.apply(createNodeJsPluginConfigurationProperties(), dataSet); + + String finalBomContent = new String(Files.readAllBytes(bomJson)); + assertTrue(finalBomContent.contains("urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79")); + } + @Test public void testDependencyCheckerNodeJsYarnNotSupported() throws IOException, URISyntaxException diff --git a/plugins/dependency-checker/src/test/java/com/freenow/sauron/plugins/generator/nodejs/NodeJsDependencyGeneratorTest.java b/plugins/dependency-checker/src/test/java/com/freenow/sauron/plugins/generator/nodejs/NodeJsDependencyGeneratorTest.java deleted file mode 100644 index 6f2360cd..00000000 --- a/plugins/dependency-checker/src/test/java/com/freenow/sauron/plugins/generator/nodejs/NodeJsDependencyGeneratorTest.java +++ /dev/null @@ -1,112 +0,0 @@ -package com.freenow.sauron.plugins.generator.nodejs; - -import com.freenow.sauron.plugins.command.Command; -import com.freenow.sauron.plugins.command.NonZeroExitCodeException; -import com.freenow.sauron.properties.PluginsConfigurationProperties; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.regex.Pattern; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; -import org.mockito.MockedStatic; -import org.mockito.Mockito; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.any; - -class NodeJsDependencyGeneratorTest -{ - @TempDir - Path tempDir; - - private MockedStatic commandMockedStatic; - private Command command; - - @BeforeEach - void setup() throws IOException - { - Files.createFile(tempDir.resolve("package-lock.json")); - - commandMockedStatic = Mockito.mockStatic(Command.class); - Command.CommandBuilder commandBuilder = Mockito.mock(Command.CommandBuilder.class); - command = Mockito.mock(Command.class); - - commandMockedStatic.when(Command::builder).thenReturn(commandBuilder); - - Mockito.when(commandBuilder.commandTimeout(any())).thenReturn(commandBuilder); - Mockito.when(commandBuilder.repositoryPath(any())).thenReturn(commandBuilder); - Mockito.when(commandBuilder.commandline(any())).thenReturn(commandBuilder); - Mockito.when(commandBuilder.outputFile(any())).thenReturn(commandBuilder); - Mockito.when(commandBuilder.build()).thenReturn(command); - } - - @AfterEach - void tearDown() - { - commandMockedStatic.close(); - } - - @Test - void testGenerateCycloneDxBomWithInvalidSerialNumberIsFixed() throws IOException, InterruptedException, NonZeroExitCodeException - { - // Given - Mockito.doAnswer(invocation -> { - Path bomJson = tempDir.resolve("bom.json"); - String bomContent = "{\n" + - " \"bomFormat\": \"CycloneDX\",\n" + - " \"specVersion\": \"1.4\",\n" + - " \"serialNumber\": \"urn:uuid:***\",\n" + - " \"version\": 1,\n" + - " \"components\": []\n" + - "}"; - Files.write(bomJson, bomContent.getBytes()); - return null; - }).when(command).run(); - - PluginsConfigurationProperties properties = new PluginsConfigurationProperties(); - NodeJsDependencyGenerator generator = new NodeJsDependencyGenerator(properties); - - // When - Path cycloneDxBom = generator.generateCycloneDxBom(tempDir); - - // Then - assertTrue(Files.exists(cycloneDxBom)); - String bomContent = new String(Files.readAllBytes(cycloneDxBom)); - assertFalse(bomContent.contains("***")); - assertTrue(Pattern.compile("\"serialNumber\": \"urn:uuid:[a-f0-9\\-]{36}\"").matcher(bomContent).find()); - } - - @Test - void testGenerateCycloneDxBomWithValidSerialNumberIsNotModified() throws IOException, InterruptedException, NonZeroExitCodeException - { - // Given - String validSerialNumberLine = "\"serialNumber\": \"urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79\""; - Mockito.doAnswer(invocation -> { - Path bomJson = tempDir.resolve("bom.json"); - String bomContent = "{\n" + - " \"bomFormat\": \"CycloneDX\",\n" + - " \"specVersion\": \"1.4\",\n" + - " " + validSerialNumberLine + ",\n" + - " \"version\": 1,\n" + - " \"components\": []\n" + - "}"; - Files.write(bomJson, bomContent.getBytes()); - return null; - }).when(command).run(); - - PluginsConfigurationProperties properties = new PluginsConfigurationProperties(); - NodeJsDependencyGenerator generator = new NodeJsDependencyGenerator(properties); - - // When - Path cycloneDxBom = generator.generateCycloneDxBom(tempDir); - - // Then - assertTrue(Files.exists(cycloneDxBom)); - String bomContent = new String(Files.readAllBytes(cycloneDxBom)); - assertTrue(bomContent.contains(validSerialNumberLine)); - } -} From 4ff951182743753eecf08ebbf821401ff0cf27ea Mon Sep 17 00:00:00 2001 From: "omid.tavakoli" Date: Fri, 14 Nov 2025 13:10:59 +0100 Subject: [PATCH 4/8] removed unused dependency --- plugins/dependency-checker/pom.xml | 12 ------------ .../generator/nodejs/NodeJsDependencyGenerator.java | 1 - 2 files changed, 13 deletions(-) diff --git a/plugins/dependency-checker/pom.xml b/plugins/dependency-checker/pom.xml index 9fcd5f62..96d8c3b6 100644 --- a/plugins/dependency-checker/pom.xml +++ b/plugins/dependency-checker/pom.xml @@ -196,18 +196,6 @@ - - org.junit.jupiter - junit-jupiter-api - 5.12.2 - test - - - org.mockito - mockito-core - 5.17.0 - test - diff --git a/plugins/dependency-checker/src/main/java/com/freenow/sauron/plugins/generator/nodejs/NodeJsDependencyGenerator.java b/plugins/dependency-checker/src/main/java/com/freenow/sauron/plugins/generator/nodejs/NodeJsDependencyGenerator.java index 2f33561e..5509a86f 100644 --- a/plugins/dependency-checker/src/main/java/com/freenow/sauron/plugins/generator/nodejs/NodeJsDependencyGenerator.java +++ b/plugins/dependency-checker/src/main/java/com/freenow/sauron/plugins/generator/nodejs/NodeJsDependencyGenerator.java @@ -110,7 +110,6 @@ private Path buildCycloneDxBom(Path repositoryPath) throws IOException, Interrup .outputFile(bomJson) .build() .run(); - return bomJson; } From a82cf9f03594a23b362a193d9bcec609be2cd687 Mon Sep 17 00:00:00 2001 From: Omid Tavakoli <4619045+omidtavakoli@users.noreply.github.com> Date: Fri, 14 Nov 2025 12:08:19 +0100 Subject: [PATCH 5/8] removed service name from metrics to decrease cardinality (#196) Co-authored-by: omid.tavakoli --- .../main/java/com/freenow/sauron/service/PipelineService.java | 1 - 1 file changed, 1 deletion(-) diff --git a/sauron-service/src/main/java/com/freenow/sauron/service/PipelineService.java b/sauron-service/src/main/java/com/freenow/sauron/service/PipelineService.java index 59b87fe2..5b54b860 100644 --- a/sauron-service/src/main/java/com/freenow/sauron/service/PipelineService.java +++ b/sauron-service/src/main/java/com/freenow/sauron/service/PipelineService.java @@ -141,7 +141,6 @@ void runPlugin(String plugin, DataSet dataSet) getTimerBuilder("sauron.plugin.execution.time") .tag("plugin", plugin) - .tag("service", dataSet.getServiceName()) .register(meterRegistry).record(() -> pluginExtension.apply(pluginsProperties, dataSet)); meterRegistry.counter("sauron.plugin.executions.total", "plugin", plugin, "result", "success").increment(); From 4f1c359f1dee1b0bb848f3190d72d26e672b5013 Mon Sep 17 00:00:00 2001 From: free-now-github <70742378+free-now-github@users.noreply.github.com> Date: Fri, 14 Nov 2025 11:11:12 +0000 Subject: [PATCH 6/8] [ci skip]prepare release sauron-service-1.0.61 --- sauron-service/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sauron-service/pom.xml b/sauron-service/pom.xml index b2cd1b41..7a33ce86 100644 --- a/sauron-service/pom.xml +++ b/sauron-service/pom.xml @@ -11,7 +11,7 @@ sauron-service com.free-now.sauron - 1.0.61-SNAPSHOT + 1.0.61 Sauron Service Sauron, The all seeying eye. Dependency and version tracking. @@ -50,7 +50,7 @@ - HEAD + sauron-service-1.0.61 ${scm.url} ${scm.url} ${scm.url} From 0fdf2eda1fd13df769568aa03792e5048b706020 Mon Sep 17 00:00:00 2001 From: free-now-github <70742378+free-now-github@users.noreply.github.com> Date: Fri, 14 Nov 2025 11:11:13 +0000 Subject: [PATCH 7/8] [ci skip]prepare for next development iteration --- sauron-service/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sauron-service/pom.xml b/sauron-service/pom.xml index 7a33ce86..6aba3c46 100644 --- a/sauron-service/pom.xml +++ b/sauron-service/pom.xml @@ -11,7 +11,7 @@ sauron-service com.free-now.sauron - 1.0.61 + 1.0.62-SNAPSHOT Sauron Service Sauron, The all seeying eye. Dependency and version tracking. @@ -50,7 +50,7 @@ - sauron-service-1.0.61 + HEAD ${scm.url} ${scm.url} ${scm.url} From 7e3cefcf7788fba215522c9cc91e612bf3163d44 Mon Sep 17 00:00:00 2001 From: "omid.tavakoli" Date: Fri, 14 Nov 2025 16:16:56 +0100 Subject: [PATCH 8/8] tests updated --- .../sauron/plugins/DependencyChecker.java | 1 + .../sauron/plugins/DependencyCheckerTest.java | 149 ++++++++++++------ 2 files changed, 105 insertions(+), 45 deletions(-) diff --git a/plugins/dependency-checker/src/main/java/com/freenow/sauron/plugins/DependencyChecker.java b/plugins/dependency-checker/src/main/java/com/freenow/sauron/plugins/DependencyChecker.java index cd9e7352..aa648fff 100644 --- a/plugins/dependency-checker/src/main/java/com/freenow/sauron/plugins/DependencyChecker.java +++ b/plugins/dependency-checker/src/main/java/com/freenow/sauron/plugins/DependencyChecker.java @@ -104,6 +104,7 @@ private List parseCycloneDxJson(Path bom) throws IOException { log.debug("Replacing invalid serialNumber in {} for project: {}", bom.getFileName(), bom.getParent()); ((ObjectNode) bomNode).put("serialNumber", "urn:uuid:" + UUID.randomUUID()); + oMapper.writeValue(bom.toFile(), bomNode); } Bom bomObject = oMapper.treeToValue(bomNode, Bom.class); diff --git a/plugins/dependency-checker/src/test/java/com/freenow/sauron/plugins/DependencyCheckerTest.java b/plugins/dependency-checker/src/test/java/com/freenow/sauron/plugins/DependencyCheckerTest.java index e7e5daeb..29f5db8d 100644 --- a/plugins/dependency-checker/src/test/java/com/freenow/sauron/plugins/DependencyCheckerTest.java +++ b/plugins/dependency-checker/src/test/java/com/freenow/sauron/plugins/DependencyCheckerTest.java @@ -3,7 +3,10 @@ import com.freenow.sauron.model.DataSet; import com.freenow.sauron.properties.PluginsConfigurationProperties; import com.github.tomakehurst.wiremock.junit.WireMockRule; -import java.util.regex.Pattern; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.charset.StandardCharsets; +import org.cyclonedx.model.Component; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -15,6 +18,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; @@ -221,50 +225,7 @@ public void testDependencyCheckerNodeJs() throws IOException, URISyntaxException )) ); } - - @Test - public void testSerialNumberSanitization() throws IOException, URISyntaxException - { - // Test with invalid serial number - DataSet dataSet = createDataSet(Map.of( - "package.json", "package.json", - "package-lock.json", "package-lock.json" - )); - String invalidBomContent = "{\n" + - " \"bomFormat\": \"CycloneDX\",\n" + - " \"specVersion\": \"1.4\",\n" + - " \"serialNumber\": \"urn:uuid:***\",\n" + - " \"version\": 1,\n" + - " \"components\": []\n" + - "}"; - Path bomJson = tempFolder.getRoot().toPath().resolve("bom.json"); - Files.write(bomJson, invalidBomContent.getBytes()); - dataSet.setAdditionalInformation("cycloneDxBomPath", bomJson.toString()); - - plugin.apply(createNodeJsPluginConfigurationProperties(), dataSet); - - String sanitizedBomContent = new String(Files.readAllBytes(bomJson)); - assertFalse(sanitizedBomContent.contains("***")); - assertTrue(Pattern.compile("\"serialNumber\": \"urn:uuid:[a-f0-9\\-]{36}\"").matcher(sanitizedBomContent).find()); - - // Test with valid serial number which should be kept unchanged - String validBomContent = "{\n" + - " \"bomFormat\": \"CycloneDX\",\n" + - " \"specVersion\": \"1.4\",\n" + - " \"serialNumber\": \"urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79\",\n" + - " \"version\": 1,\n" + - " \"components\": []\n" + - "}"; - Files.write(bomJson, validBomContent.getBytes()); - dataSet.setAdditionalInformation("cycloneDxBomPath", bomJson.toString()); - - plugin.apply(createNodeJsPluginConfigurationProperties(), dataSet); - - String finalBomContent = new String(Files.readAllBytes(bomJson)); - assertTrue(finalBomContent.contains("urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79")); - } - - + @Test public void testDependencyCheckerNodeJsYarnNotSupported() throws IOException, URISyntaxException { @@ -483,4 +444,102 @@ private PluginsConfigurationProperties pluginConfigurationProperties() return properties; } + + @Test + public void testParseCycloneDxJsonWithInvalidSerialNumber() throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException + { + // Given + String invalidBomContent = "{\n" + + " \"bomFormat\": \"CycloneDX\",\n" + + " \"specVersion\": \"1.4\",\n" + + " \"serialNumber\": \"urn:uuid:***\",\n" + + " \"version\": 1,\n" + + " \"components\": [\n" + + " {\n" + + " \"type\": \"library\",\n" + + " \"name\": \"react\",\n" + + " \"version\": \"18.2.0\"\n" + + " }\n" + + " ]\n" + + "}"; + + Path bomJson = tempFolder.getRoot().toPath().resolve("bom.json"); + Files.write(bomJson, invalidBomContent.getBytes(StandardCharsets.UTF_8)); + + // When + List components = invokeParseCycloneDxJson(plugin, bomJson); + + // Then + assertNotNull(components); + assertEquals(1, components.size()); + assertEquals("react", components.get(0).getName()); + assertEquals("18.2.0", components.get(0).getVersion()); + + String sanitizedBomContent = new String(Files.readAllBytes(bomJson), StandardCharsets.UTF_8); + assertFalse("The serialNumber should have been sanitized", sanitizedBomContent.contains("***")); + } + + @Test + public void testParseCycloneDxJsonWithValidBom() throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException + { + // Given + String validBomContent = "{\n" + + " \"bomFormat\": \"CycloneDX\",\n" + + " \"specVersion\": \"1.4\",\n" + + " \"serialNumber\": \"urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79\",\n" + + " \"version\": 1,\n" + + " \"components\": [\n" + + " {\n" + + " \"type\": \"library\",\n" + + " \"name\": \"express\",\n" + + " \"version\": \"4.18.2\"\n" + + " }\n" + + " ]\n" + + "}"; + + Path bomJson = tempFolder.getRoot().toPath().resolve("bom.json"); + Files.write(bomJson, validBomContent.getBytes(StandardCharsets.UTF_8)); + + // When + List components = invokeParseCycloneDxJson(plugin, bomJson); + + // Then + assertNotNull(components); + assertEquals(1, components.size()); + assertEquals("express", components.get(0).getName()); + assertEquals("4.18.2", components.get(0).getVersion()); + + String bomContent = new String(Files.readAllBytes(bomJson), StandardCharsets.UTF_8); + assertEquals("The BOM file should not be modified", validBomContent, bomContent); + } + + @Test + public void testParseCycloneDxJsonWithNoComponents() throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException + { + // Given + String bomWithNoComponents = "{\n" + + " \"bomFormat\": \"CycloneDX\",\n" + + " \"specVersion\": \"1.4\",\n" + + " \"serialNumber\": \"urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79\",\n" + + " \"version\": 1,\n" + + " \"components\": []\n" + + "}"; + + Path bomJson = tempFolder.getRoot().toPath().resolve("bom.json"); + Files.write(bomJson, bomWithNoComponents.getBytes(StandardCharsets.UTF_8)); + + // When + List components = invokeParseCycloneDxJson(plugin, bomJson); + + // Then + assertNotNull(components); + assertTrue(components.isEmpty()); + } + + @SuppressWarnings("unchecked") + private List invokeParseCycloneDxJson(DependencyChecker plugin, Path bom) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { + Method method = DependencyChecker.class.getDeclaredMethod("parseCycloneDxJson", Path.class); + method.setAccessible(true); + return (List) method.invoke(plugin, bom); + } }