From c619a741dadcc6a265ddfb27732387f75b1c0f7d Mon Sep 17 00:00:00 2001 From: Jordan Hury Date: Tue, 22 Sep 2026 20:10:48 +0300 Subject: [PATCH 01/10] Merge Gradle module trees instead of replacing shared nodes A component that appears in more than one module's dependency-tree file may be resolved there with different transitive dependencies. Replacing the node let the last file win, which could leave a transitive dependency with no parent at all and crash impact-graph construction. --- .../ide/common/gradle/GradleTreeBuilder.java | 7 ++++-- .../common/gradle/GradleTreeBuilderTest.java | 22 ++++++++++++++++ .../gradle/sharedDependency/build.gradle | 25 +++++++++++++++++++ .../moda/src/main/java/Moda.java | 2 ++ .../modb/src/main/java/Modb.java | 2 ++ .../gradle/sharedDependency/settings.gradle | 1 + 6 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 src/test/resources/gradle/sharedDependency/build.gradle create mode 100644 src/test/resources/gradle/sharedDependency/moda/src/main/java/Moda.java create mode 100644 src/test/resources/gradle/sharedDependency/modb/src/main/java/Modb.java create mode 100644 src/test/resources/gradle/sharedDependency/settings.gradle diff --git a/src/main/java/com/jfrog/ide/common/gradle/GradleTreeBuilder.java b/src/main/java/com/jfrog/ide/common/gradle/GradleTreeBuilder.java index 65f34053..26182aed 100644 --- a/src/main/java/com/jfrog/ide/common/gradle/GradleTreeBuilder.java +++ b/src/main/java/com/jfrog/ide/common/gradle/GradleTreeBuilder.java @@ -50,6 +50,8 @@ public DepTree buildTree(Log logger) throws IOException { /** * Create dependency trees from files generated by running the 'generateDependenciesGraphAsJson' task. + * A component that appears in more than one module's file may be resolved there with a different set of + * configurations and transitive dependencies, so the nodes are merged rather than replaced. * * @param gradleDependenciesFiles - The files containing the dependency trees * @return a dependency tree contain one or more Gradle projects. @@ -65,8 +67,9 @@ private DepTree createDependencyTrees(List gradleDependenciesFiles) throws for (Map.Entry nodeEntry : results.getNodes().entrySet()) { String compId = nodeEntry.getKey(); GradleDependencyNode gradleDep = nodeEntry.getValue(); - DepTreeNode node = new DepTreeNode().scopes(gradleDep.getConfigurations()).children(gradleDep.getChildren()); - nodes.put(compId, node); + DepTreeNode node = nodes.computeIfAbsent(compId, id -> new DepTreeNode()); + node.getScopes().addAll(gradleDep.getConfigurations()); + node.getChildren().addAll(gradleDep.getChildren()); } String moduleRootId = results.getRoot(); nodes.get(moduleRootId).descriptorFilePath(descriptorFilePath); diff --git a/src/test/java/com/jfrog/ide/common/gradle/GradleTreeBuilderTest.java b/src/test/java/com/jfrog/ide/common/gradle/GradleTreeBuilderTest.java index 564ae701..c7be056c 100644 --- a/src/test/java/com/jfrog/ide/common/gradle/GradleTreeBuilderTest.java +++ b/src/test/java/com/jfrog/ide/common/gradle/GradleTreeBuilderTest.java @@ -80,6 +80,28 @@ public void gradleTreeBuilderUnresolvedTest(String projectPath) throws IOExcepti assertTrue(missing.getScopes().contains("testImplementation")); } + /** + * Data provider for a project whose modules resolve the same dependency with different transitive + * dependencies - 'modb' excludes 'commons-lang3' from 'commons-text', 'moda' doesn't. + * + * @return 'sharedDependency'. + */ + @DataProvider + private Object[][] gradleTreeBuilderSharedDependencyProvider() { + return new Object[][]{{"sharedDependency"}}; + } + + @SuppressWarnings("unused") + @Test(dataProvider = "gradleTreeBuilderSharedDependencyProvider") + public void gradleTreeBuilderSharedDependencyTest(String projectPath) throws IOException { + DepTree depTree = buildGradleDependencyTree(projectPath); + + DepTreeNode commonsText = depTree.nodes().get("org.apache.commons:commons-text:1.9"); + assertNotNull(commonsText, "Couldn't find node 'org.apache.commons:commons-text:1.9'."); + assertTrue(commonsText.getChildren().contains("org.apache.commons:commons-lang3:3.11"), + "The dependency resolved in 'moda' was dropped by the module of 'modb': " + commonsText.getChildren()); + } + private DepTree buildGradleDependencyTree(String projectPath) throws IOException { // Add path to gradle-dep-tree JAR to "pluginLibDir" environment variable, to be read in gradle-dep-tree.gradle init script Map env = new HashMap<>(System.getenv()); diff --git a/src/test/resources/gradle/sharedDependency/build.gradle b/src/test/resources/gradle/sharedDependency/build.gradle new file mode 100644 index 00000000..795af685 --- /dev/null +++ b/src/test/resources/gradle/sharedDependency/build.gradle @@ -0,0 +1,25 @@ +allprojects { + group = 'org.jfrog.test.gradle.shared' + version = '1.0-SNAPSHOT' + repositories { + mavenCentral() + } +} + +subprojects { + apply plugin: 'java' +} + +project('moda') { + dependencies { + implementation 'org.apache.commons:commons-text:1.9' + } +} + +project('modb') { + dependencies { + implementation('org.apache.commons:commons-text:1.9') { + exclude group: 'org.apache.commons', module: 'commons-lang3' + } + } +} diff --git a/src/test/resources/gradle/sharedDependency/moda/src/main/java/Moda.java b/src/test/resources/gradle/sharedDependency/moda/src/main/java/Moda.java new file mode 100644 index 00000000..2b6fbcdf --- /dev/null +++ b/src/test/resources/gradle/sharedDependency/moda/src/main/java/Moda.java @@ -0,0 +1,2 @@ +public class Moda { +} diff --git a/src/test/resources/gradle/sharedDependency/modb/src/main/java/Modb.java b/src/test/resources/gradle/sharedDependency/modb/src/main/java/Modb.java new file mode 100644 index 00000000..91fc5298 --- /dev/null +++ b/src/test/resources/gradle/sharedDependency/modb/src/main/java/Modb.java @@ -0,0 +1,2 @@ +public class Modb { +} diff --git a/src/test/resources/gradle/sharedDependency/settings.gradle b/src/test/resources/gradle/sharedDependency/settings.gradle new file mode 100644 index 00000000..2e05e372 --- /dev/null +++ b/src/test/resources/gradle/sharedDependency/settings.gradle @@ -0,0 +1 @@ +include "moda", "modb" From 22818e97be2691e7a911959f5029540d20f318e1 Mon Sep 17 00:00:00 2001 From: Jordan Hury Date: Tue, 22 Sep 2026 20:11:53 +0300 Subject: [PATCH 02/10] Add per-module scopes to DepTree Co-Authored-By: Claude Haiku 4.5 --- .../com/jfrog/ide/common/deptree/DepTree.java | 13 +++++++--- .../ide/common/deptree/DepTreeModule.java | 12 +++++++++ .../jfrog/ide/common/deptree/DepTreeTest.java | 25 +++++++++++++++++++ 3 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/jfrog/ide/common/deptree/DepTreeModule.java create mode 100644 src/test/java/com/jfrog/ide/common/deptree/DepTreeTest.java diff --git a/src/main/java/com/jfrog/ide/common/deptree/DepTree.java b/src/main/java/com/jfrog/ide/common/deptree/DepTree.java index 9ffd4092..5dca8e7a 100644 --- a/src/main/java/com/jfrog/ide/common/deptree/DepTree.java +++ b/src/main/java/com/jfrog/ide/common/deptree/DepTree.java @@ -1,14 +1,21 @@ package com.jfrog.ide.common.deptree; +import java.util.List; import java.util.Map; /** * Represents a dependency tree. * - * @param rootId The root node ID - * @param nodes A map of the nodes in the tree by their component IDs + * @param rootId The root node ID + * @param nodes A map of the nodes in the tree by their component IDs, merged across all modules + * @param modules The per-module trees, for projects whose modules may resolve a component differently. + * Empty for single-module ecosystems. */ -public record DepTree(String rootId, Map nodes) { +public record DepTree(String rootId, Map nodes, List modules) { + + public DepTree(String rootId, Map nodes) { + this(rootId, nodes, List.of()); + } public DepTreeNode getRootNode() { return nodes.get(rootId); diff --git a/src/main/java/com/jfrog/ide/common/deptree/DepTreeModule.java b/src/main/java/com/jfrog/ide/common/deptree/DepTreeModule.java new file mode 100644 index 00000000..27f89a80 --- /dev/null +++ b/src/main/java/com/jfrog/ide/common/deptree/DepTreeModule.java @@ -0,0 +1,12 @@ +package com.jfrog.ide.common.deptree; + +import java.util.Map; + +/** + * The dependency tree of a single module of a multi-module project, as resolved by that module. + * + * @param rootId the module's root node ID + * @param nodes the nodes reachable from the module's root, by their component IDs + */ +public record DepTreeModule(String rootId, Map nodes) { +} diff --git a/src/test/java/com/jfrog/ide/common/deptree/DepTreeTest.java b/src/test/java/com/jfrog/ide/common/deptree/DepTreeTest.java new file mode 100644 index 00000000..21e9ae80 --- /dev/null +++ b/src/test/java/com/jfrog/ide/common/deptree/DepTreeTest.java @@ -0,0 +1,25 @@ +package com.jfrog.ide.common.deptree; + +import org.testng.annotations.Test; + +import java.util.List; +import java.util.Map; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +public class DepTreeTest { + @Test + public void twoArgConstructorHasNoModules() { + DepTree depTree = new DepTree("root", Map.of("root", new DepTreeNode())); + assertTrue(depTree.modules().isEmpty()); + } + + @Test + public void modulesArePreserved() { + DepTreeModule module = new DepTreeModule("moda", Map.of("moda", new DepTreeNode())); + DepTree depTree = new DepTree("root", Map.of("root", new DepTreeNode()), List.of(module)); + assertEquals(depTree.modules(), List.of(module)); + assertEquals(depTree.modules().get(0).rootId(), "moda"); + } +} From 10fc1f0c5967ca9c4587b867dcfbfb5809427a3c Mon Sep 17 00:00:00 2001 From: Jordan Hury Date: Tue, 22 Sep 2026 20:14:27 +0300 Subject: [PATCH 03/10] Populate per-module scopes when merging Gradle module trees Co-Authored-By: Claude Sonnet 5 --- .../ide/common/gradle/GradleTreeBuilder.java | 13 ++++++++-- .../common/gradle/GradleTreeBuilderTest.java | 24 +++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/jfrog/ide/common/gradle/GradleTreeBuilder.java b/src/main/java/com/jfrog/ide/common/gradle/GradleTreeBuilder.java index 26182aed..0f3b3138 100644 --- a/src/main/java/com/jfrog/ide/common/gradle/GradleTreeBuilder.java +++ b/src/main/java/com/jfrog/ide/common/gradle/GradleTreeBuilder.java @@ -4,6 +4,7 @@ import com.jfrog.GradleDepTreeResults; import com.jfrog.GradleDependencyNode; import com.jfrog.ide.common.deptree.DepTree; +import com.jfrog.ide.common.deptree.DepTreeModule; import com.jfrog.ide.common.deptree.DepTreeNode; import org.jfrog.build.api.util.Log; import org.jfrog.build.extractor.scan.GeneralInfo; @@ -11,7 +12,9 @@ import java.io.File; import java.io.IOException; import java.nio.file.Path; +import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; @@ -62,11 +65,16 @@ private DepTree createDependencyTrees(List gradleDependenciesFiles) throws DepTreeNode rootNode = new DepTreeNode().descriptorFilePath(descriptorFilePath); Map nodes = new HashMap<>(); + List modules = new ArrayList<>(); for (File moduleDepsFile : gradleDependenciesFiles) { GradleDepTreeResults results = objectMapper.readValue(moduleDepsFile, GradleDepTreeResults.class); + Map moduleNodes = new HashMap<>(); for (Map.Entry nodeEntry : results.getNodes().entrySet()) { String compId = nodeEntry.getKey(); GradleDependencyNode gradleDep = nodeEntry.getValue(); + moduleNodes.put(compId, new DepTreeNode() + .scopes(new HashSet<>(gradleDep.getConfigurations())) + .children(new HashSet<>(gradleDep.getChildren()))); DepTreeNode node = nodes.computeIfAbsent(compId, id -> new DepTreeNode()); node.getScopes().addAll(gradleDep.getConfigurations()); node.getChildren().addAll(gradleDep.getChildren()); @@ -74,12 +82,13 @@ private DepTree createDependencyTrees(List gradleDependenciesFiles) throws String moduleRootId = results.getRoot(); nodes.get(moduleRootId).descriptorFilePath(descriptorFilePath); rootNode.getChildren().add(moduleRootId); + modules.add(new DepTreeModule(moduleRootId, moduleNodes)); } if (rootNode.getChildren().size() == 1) { - return new DepTree(rootNode.getChildren().iterator().next(), nodes); + return new DepTree(rootNode.getChildren().iterator().next(), nodes, modules); } nodes.put(rootId, rootNode); - return new DepTree(rootId, nodes); + return new DepTree(rootId, nodes, modules); } private GeneralInfo createGeneralInfo(String id, GradleDependencyNode node) { diff --git a/src/test/java/com/jfrog/ide/common/gradle/GradleTreeBuilderTest.java b/src/test/java/com/jfrog/ide/common/gradle/GradleTreeBuilderTest.java index c7be056c..6007f474 100644 --- a/src/test/java/com/jfrog/ide/common/gradle/GradleTreeBuilderTest.java +++ b/src/test/java/com/jfrog/ide/common/gradle/GradleTreeBuilderTest.java @@ -3,6 +3,7 @@ import com.jfrog.GradleDependencyNode; import com.jfrog.ide.common.TestUtils; import com.jfrog.ide.common.deptree.DepTree; +import com.jfrog.ide.common.deptree.DepTreeModule; import com.jfrog.ide.common.deptree.DepTreeNode; import org.apache.commons.io.FileUtils; import org.jfrog.build.api.util.NullLog; @@ -102,6 +103,29 @@ public void gradleTreeBuilderSharedDependencyTest(String projectPath) throws IOE "The dependency resolved in 'moda' was dropped by the module of 'modb': " + commonsText.getChildren()); } + @SuppressWarnings("unused") + @Test(dataProvider = "gradleTreeBuilderSharedDependencyProvider") + public void gradleTreeBuilderModuleScopesTest(String projectPath) throws IOException { + DepTree depTree = buildGradleDependencyTree(projectPath); + + Map modulesByRoot = new HashMap<>(); + for (DepTreeModule module : depTree.modules()) { + modulesByRoot.put(module.rootId(), module); + } + assertEquals(modulesByRoot.size(), 3); + + DepTreeModule moda = modulesByRoot.get("org.jfrog.test.gradle.shared:moda:1.0-SNAPSHOT"); + assertNotNull(moda, "Couldn't find the 'moda' module scope in " + modulesByRoot.keySet()); + assertTrue(moda.nodes().get("org.apache.commons:commons-text:1.9").getChildren() + .contains("org.apache.commons:commons-lang3:3.11"), + "'moda' resolves commons-lang3 through commons-text"); + + DepTreeModule modb = modulesByRoot.get("org.jfrog.test.gradle.shared:modb:1.0-SNAPSHOT"); + assertNotNull(modb, "Couldn't find the 'modb' module scope in " + modulesByRoot.keySet()); + assertFalse(modb.nodes().containsKey("org.apache.commons:commons-lang3:3.11"), + "'modb' excludes commons-lang3, so its scope must not contain it"); + } + private DepTree buildGradleDependencyTree(String projectPath) throws IOException { // Add path to gradle-dep-tree JAR to "pluginLibDir" environment variable, to be read in gradle-dep-tree.gradle init script Map env = new HashMap<>(System.getenv()); From fcf35721e545e5167ead0c26eb23b009026abdf1 Mon Sep 17 00:00:00 2001 From: Jordan Hury Date: Tue, 22 Sep 2026 20:18:43 +0300 Subject: [PATCH 04/10] Strengthen gradleTreeBuilderModuleScopesTest to catch node aliasing The prior assertions on 'modb' were vacuously true whether or not module scopes aliased the merged node map, since 'modb' never resolves commons-lang3 in its own file. Assert on modb's own commons-text node's children instead, which does regress if a module's DepTreeNode is aliased to the merged map's node. Co-Authored-By: Claude Sonnet 5 --- .../com/jfrog/ide/common/gradle/GradleTreeBuilderTest.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/test/java/com/jfrog/ide/common/gradle/GradleTreeBuilderTest.java b/src/test/java/com/jfrog/ide/common/gradle/GradleTreeBuilderTest.java index 6007f474..602b59a9 100644 --- a/src/test/java/com/jfrog/ide/common/gradle/GradleTreeBuilderTest.java +++ b/src/test/java/com/jfrog/ide/common/gradle/GradleTreeBuilderTest.java @@ -124,6 +124,9 @@ public void gradleTreeBuilderModuleScopesTest(String projectPath) throws IOExcep assertNotNull(modb, "Couldn't find the 'modb' module scope in " + modulesByRoot.keySet()); assertFalse(modb.nodes().containsKey("org.apache.commons:commons-lang3:3.11"), "'modb' excludes commons-lang3, so its scope must not contain it"); + assertFalse(modb.nodes().get("org.apache.commons:commons-text:1.9").getChildren() + .contains("org.apache.commons:commons-lang3:3.11"), + "'modb' excludes commons-lang3 from commons-text, so modb's own commons-text node must not include it"); } private DepTree buildGradleDependencyTree(String projectPath) throws IOException { From a8b398998b6417082a1f0c3cc43608dd92a54d87 Mon Sep 17 00:00:00 2001 From: Jordan Hury Date: Tue, 22 Sep 2026 20:42:17 +0300 Subject: [PATCH 05/10] Address final review of the per-module Gradle trees Co-Authored-By: Claude Sonnet 5 --- .../com/jfrog/ide/common/deptree/DepTree.java | 2 +- .../ide/common/gradle/GradleTreeBuilder.java | 1 + .../jfrog/ide/common/deptree/DepTreeTest.java | 10 ---------- .../common/gradle/GradleTreeBuilderTest.java | 20 +++++++++++++------ 4 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/jfrog/ide/common/deptree/DepTree.java b/src/main/java/com/jfrog/ide/common/deptree/DepTree.java index 5dca8e7a..b632c2a9 100644 --- a/src/main/java/com/jfrog/ide/common/deptree/DepTree.java +++ b/src/main/java/com/jfrog/ide/common/deptree/DepTree.java @@ -9,7 +9,7 @@ * @param rootId The root node ID * @param nodes A map of the nodes in the tree by their component IDs, merged across all modules * @param modules The per-module trees, for projects whose modules may resolve a component differently. - * Empty for single-module ecosystems. + * Empty for ecosystems that don't report per-module dependency trees. */ public record DepTree(String rootId, Map nodes, List modules) { diff --git a/src/main/java/com/jfrog/ide/common/gradle/GradleTreeBuilder.java b/src/main/java/com/jfrog/ide/common/gradle/GradleTreeBuilder.java index 0f3b3138..4ee70a26 100644 --- a/src/main/java/com/jfrog/ide/common/gradle/GradleTreeBuilder.java +++ b/src/main/java/com/jfrog/ide/common/gradle/GradleTreeBuilder.java @@ -81,6 +81,7 @@ private DepTree createDependencyTrees(List gradleDependenciesFiles) throws } String moduleRootId = results.getRoot(); nodes.get(moduleRootId).descriptorFilePath(descriptorFilePath); + moduleNodes.get(moduleRootId).descriptorFilePath(descriptorFilePath); rootNode.getChildren().add(moduleRootId); modules.add(new DepTreeModule(moduleRootId, moduleNodes)); } diff --git a/src/test/java/com/jfrog/ide/common/deptree/DepTreeTest.java b/src/test/java/com/jfrog/ide/common/deptree/DepTreeTest.java index 21e9ae80..e03c1527 100644 --- a/src/test/java/com/jfrog/ide/common/deptree/DepTreeTest.java +++ b/src/test/java/com/jfrog/ide/common/deptree/DepTreeTest.java @@ -2,10 +2,8 @@ import org.testng.annotations.Test; -import java.util.List; import java.util.Map; -import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; public class DepTreeTest { @@ -14,12 +12,4 @@ public void twoArgConstructorHasNoModules() { DepTree depTree = new DepTree("root", Map.of("root", new DepTreeNode())); assertTrue(depTree.modules().isEmpty()); } - - @Test - public void modulesArePreserved() { - DepTreeModule module = new DepTreeModule("moda", Map.of("moda", new DepTreeNode())); - DepTree depTree = new DepTree("root", Map.of("root", new DepTreeNode()), List.of(module)); - assertEquals(depTree.modules(), List.of(module)); - assertEquals(depTree.modules().get(0).rootId(), "moda"); - } } diff --git a/src/test/java/com/jfrog/ide/common/gradle/GradleTreeBuilderTest.java b/src/test/java/com/jfrog/ide/common/gradle/GradleTreeBuilderTest.java index 602b59a9..5c5cf344 100644 --- a/src/test/java/com/jfrog/ide/common/gradle/GradleTreeBuilderTest.java +++ b/src/test/java/com/jfrog/ide/common/gradle/GradleTreeBuilderTest.java @@ -105,7 +105,7 @@ public void gradleTreeBuilderSharedDependencyTest(String projectPath) throws IOE @SuppressWarnings("unused") @Test(dataProvider = "gradleTreeBuilderSharedDependencyProvider") - public void gradleTreeBuilderModuleScopesTest(String projectPath) throws IOException { + public void gradleTreeBuilderModulesTest(String projectPath) throws IOException { DepTree depTree = buildGradleDependencyTree(projectPath); Map modulesByRoot = new HashMap<>(); @@ -114,16 +114,24 @@ public void gradleTreeBuilderModuleScopesTest(String projectPath) throws IOExcep } assertEquals(modulesByRoot.size(), 3); + String rootModuleId = "org.jfrog.test.gradle.shared:" + tempProject.getName() + ":1.0-SNAPSHOT"; + assertTrue(modulesByRoot.containsKey(rootModuleId), + "Couldn't find the root project's own module tree '" + rootModuleId + "' in " + modulesByRoot.keySet()); + DepTreeModule moda = modulesByRoot.get("org.jfrog.test.gradle.shared:moda:1.0-SNAPSHOT"); - assertNotNull(moda, "Couldn't find the 'moda' module scope in " + modulesByRoot.keySet()); - assertTrue(moda.nodes().get("org.apache.commons:commons-text:1.9").getChildren() - .contains("org.apache.commons:commons-lang3:3.11"), + assertNotNull(moda, "Couldn't find the 'moda' module tree in " + modulesByRoot.keySet()); + DepTreeNode modaCommonsText = moda.nodes().get("org.apache.commons:commons-text:1.9"); + assertEquals(modaCommonsText.getScopes(), Sets.newHashSet( + "implementation", "compileClasspath", "runtimeClasspath", + "testCompileClasspath", "testRuntimeClasspath", "default"), + "'moda' resolves commons-text through its own configurations"); + assertTrue(modaCommonsText.getChildren().contains("org.apache.commons:commons-lang3:3.11"), "'moda' resolves commons-lang3 through commons-text"); DepTreeModule modb = modulesByRoot.get("org.jfrog.test.gradle.shared:modb:1.0-SNAPSHOT"); - assertNotNull(modb, "Couldn't find the 'modb' module scope in " + modulesByRoot.keySet()); + assertNotNull(modb, "Couldn't find the 'modb' module tree in " + modulesByRoot.keySet()); assertFalse(modb.nodes().containsKey("org.apache.commons:commons-lang3:3.11"), - "'modb' excludes commons-lang3, so its scope must not contain it"); + "'modb' excludes commons-lang3, so its own tree must not contain it"); assertFalse(modb.nodes().get("org.apache.commons:commons-text:1.9").getChildren() .contains("org.apache.commons:commons-lang3:3.11"), "'modb' excludes commons-lang3 from commons-text, so modb's own commons-text node must not include it"); From 2f5360075db8a782f6d1afc937583e34d28819e5 Mon Sep 17 00:00:00 2001 From: Jordan Hury Date: Wed, 23 Sep 2026 14:23:53 +0300 Subject: [PATCH 06/10] Split Gradle tree assembly into reading and merging Read each module's dependency-tree file into its own module tree first, then merge those, so each step reads on its own. Fold the two shared-dependency tests into one and drop a test that only restated the DepTree constructor. Co-Authored-By: Claude Opus 5.5 --- .../ide/common/gradle/GradleTreeBuilder.java | 64 +++++++++++-------- .../jfrog/ide/common/deptree/DepTreeTest.java | 15 ----- .../common/gradle/GradleTreeBuilderTest.java | 47 ++++---------- 3 files changed, 49 insertions(+), 77 deletions(-) delete mode 100644 src/test/java/com/jfrog/ide/common/deptree/DepTreeTest.java diff --git a/src/main/java/com/jfrog/ide/common/gradle/GradleTreeBuilder.java b/src/main/java/com/jfrog/ide/common/gradle/GradleTreeBuilder.java index 4ee70a26..79319978 100644 --- a/src/main/java/com/jfrog/ide/common/gradle/GradleTreeBuilder.java +++ b/src/main/java/com/jfrog/ide/common/gradle/GradleTreeBuilder.java @@ -17,6 +17,8 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; /** * Build Gradle dependency tree before the Xray scan. @@ -53,45 +55,55 @@ public DepTree buildTree(Log logger) throws IOException { /** * Create dependency trees from files generated by running the 'generateDependenciesGraphAsJson' task. - * A component that appears in more than one module's file may be resolved there with a different set of - * configurations and transitive dependencies, so the nodes are merged rather than replaced. * * @param gradleDependenciesFiles - The files containing the dependency trees * @return a dependency tree contain one or more Gradle projects. * @throws IOException in case of any I/O error. */ private DepTree createDependencyTrees(List gradleDependenciesFiles) throws IOException { - String rootId = projectDir.getFileName().toString(); - DepTreeNode rootNode = new DepTreeNode().descriptorFilePath(descriptorFilePath); - - Map nodes = new HashMap<>(); List modules = new ArrayList<>(); for (File moduleDepsFile : gradleDependenciesFiles) { - GradleDepTreeResults results = objectMapper.readValue(moduleDepsFile, GradleDepTreeResults.class); - Map moduleNodes = new HashMap<>(); - for (Map.Entry nodeEntry : results.getNodes().entrySet()) { - String compId = nodeEntry.getKey(); - GradleDependencyNode gradleDep = nodeEntry.getValue(); - moduleNodes.put(compId, new DepTreeNode() - .scopes(new HashSet<>(gradleDep.getConfigurations())) - .children(new HashSet<>(gradleDep.getChildren()))); - DepTreeNode node = nodes.computeIfAbsent(compId, id -> new DepTreeNode()); - node.getScopes().addAll(gradleDep.getConfigurations()); - node.getChildren().addAll(gradleDep.getChildren()); - } - String moduleRootId = results.getRoot(); - nodes.get(moduleRootId).descriptorFilePath(descriptorFilePath); - moduleNodes.get(moduleRootId).descriptorFilePath(descriptorFilePath); - rootNode.getChildren().add(moduleRootId); - modules.add(new DepTreeModule(moduleRootId, moduleNodes)); + modules.add(readModule(moduleDepsFile)); } - if (rootNode.getChildren().size() == 1) { - return new DepTree(rootNode.getChildren().iterator().next(), nodes, modules); + Map nodes = mergeModules(modules); + Set moduleRootIds = modules.stream().map(DepTreeModule::rootId).collect(Collectors.toCollection(HashSet::new)); + if (moduleRootIds.size() == 1) { + return new DepTree(moduleRootIds.iterator().next(), nodes, modules); } - nodes.put(rootId, rootNode); + String rootId = projectDir.getFileName().toString(); + nodes.put(rootId, new DepTreeNode().descriptorFilePath(descriptorFilePath).children(moduleRootIds)); return new DepTree(rootId, nodes, modules); } + private DepTreeModule readModule(File moduleDepsFile) throws IOException { + GradleDepTreeResults results = objectMapper.readValue(moduleDepsFile, GradleDepTreeResults.class); + Map nodes = new HashMap<>(); + results.getNodes().forEach((compId, gradleDep) -> nodes.put(compId, new DepTreeNode() + .scopes(new HashSet<>(gradleDep.getConfigurations())) + .children(new HashSet<>(gradleDep.getChildren())))); + nodes.get(results.getRoot()).descriptorFilePath(descriptorFilePath); + return new DepTreeModule(results.getRoot(), nodes); + } + + /** + * A component shared by several modules may be resolved with different configurations and transitive + * dependencies in each, so its nodes are merged rather than replaced. + */ + private Map mergeModules(List modules) { + Map merged = new HashMap<>(); + for (DepTreeModule module : modules) { + module.nodes().forEach((compId, moduleNode) -> { + DepTreeNode node = merged.computeIfAbsent(compId, id -> new DepTreeNode()); + node.getScopes().addAll(moduleNode.getScopes()); + node.getChildren().addAll(moduleNode.getChildren()); + if (moduleNode.getDescriptorFilePath() != null) { + node.descriptorFilePath(moduleNode.getDescriptorFilePath()); + } + }); + } + return merged; + } + private GeneralInfo createGeneralInfo(String id, GradleDependencyNode node) { return new GeneralInfo().pkgType("gradle").componentId(id); } diff --git a/src/test/java/com/jfrog/ide/common/deptree/DepTreeTest.java b/src/test/java/com/jfrog/ide/common/deptree/DepTreeTest.java deleted file mode 100644 index e03c1527..00000000 --- a/src/test/java/com/jfrog/ide/common/deptree/DepTreeTest.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.jfrog.ide.common.deptree; - -import org.testng.annotations.Test; - -import java.util.Map; - -import static org.testng.Assert.assertTrue; - -public class DepTreeTest { - @Test - public void twoArgConstructorHasNoModules() { - DepTree depTree = new DepTree("root", Map.of("root", new DepTreeNode())); - assertTrue(depTree.modules().isEmpty()); - } -} diff --git a/src/test/java/com/jfrog/ide/common/gradle/GradleTreeBuilderTest.java b/src/test/java/com/jfrog/ide/common/gradle/GradleTreeBuilderTest.java index 5c5cf344..0aa1d199 100644 --- a/src/test/java/com/jfrog/ide/common/gradle/GradleTreeBuilderTest.java +++ b/src/test/java/com/jfrog/ide/common/gradle/GradleTreeBuilderTest.java @@ -95,46 +95,21 @@ private Object[][] gradleTreeBuilderSharedDependencyProvider() { @SuppressWarnings("unused") @Test(dataProvider = "gradleTreeBuilderSharedDependencyProvider") public void gradleTreeBuilderSharedDependencyTest(String projectPath) throws IOException { + final String COMMONS_TEXT = "org.apache.commons:commons-text:1.9"; + final String COMMONS_LANG3 = "org.apache.commons:commons-lang3:3.11"; DepTree depTree = buildGradleDependencyTree(projectPath); - DepTreeNode commonsText = depTree.nodes().get("org.apache.commons:commons-text:1.9"); - assertNotNull(commonsText, "Couldn't find node 'org.apache.commons:commons-text:1.9'."); - assertTrue(commonsText.getChildren().contains("org.apache.commons:commons-lang3:3.11"), - "The dependency resolved in 'moda' was dropped by the module of 'modb': " + commonsText.getChildren()); - } - - @SuppressWarnings("unused") - @Test(dataProvider = "gradleTreeBuilderSharedDependencyProvider") - public void gradleTreeBuilderModulesTest(String projectPath) throws IOException { - DepTree depTree = buildGradleDependencyTree(projectPath); + assertTrue(depTree.nodes().get(COMMONS_TEXT).getChildren().contains(COMMONS_LANG3), + "The merged tree must keep the edge resolved by 'moda'"); Map modulesByRoot = new HashMap<>(); - for (DepTreeModule module : depTree.modules()) { - modulesByRoot.put(module.rootId(), module); - } - assertEquals(modulesByRoot.size(), 3); - - String rootModuleId = "org.jfrog.test.gradle.shared:" + tempProject.getName() + ":1.0-SNAPSHOT"; - assertTrue(modulesByRoot.containsKey(rootModuleId), - "Couldn't find the root project's own module tree '" + rootModuleId + "' in " + modulesByRoot.keySet()); - - DepTreeModule moda = modulesByRoot.get("org.jfrog.test.gradle.shared:moda:1.0-SNAPSHOT"); - assertNotNull(moda, "Couldn't find the 'moda' module tree in " + modulesByRoot.keySet()); - DepTreeNode modaCommonsText = moda.nodes().get("org.apache.commons:commons-text:1.9"); - assertEquals(modaCommonsText.getScopes(), Sets.newHashSet( - "implementation", "compileClasspath", "runtimeClasspath", - "testCompileClasspath", "testRuntimeClasspath", "default"), - "'moda' resolves commons-text through its own configurations"); - assertTrue(modaCommonsText.getChildren().contains("org.apache.commons:commons-lang3:3.11"), - "'moda' resolves commons-lang3 through commons-text"); - - DepTreeModule modb = modulesByRoot.get("org.jfrog.test.gradle.shared:modb:1.0-SNAPSHOT"); - assertNotNull(modb, "Couldn't find the 'modb' module tree in " + modulesByRoot.keySet()); - assertFalse(modb.nodes().containsKey("org.apache.commons:commons-lang3:3.11"), - "'modb' excludes commons-lang3, so its own tree must not contain it"); - assertFalse(modb.nodes().get("org.apache.commons:commons-text:1.9").getChildren() - .contains("org.apache.commons:commons-lang3:3.11"), - "'modb' excludes commons-lang3 from commons-text, so modb's own commons-text node must not include it"); + depTree.modules().forEach(module -> modulesByRoot.put(module.rootId(), module)); + DepTreeNode modaCommonsText = modulesByRoot.get("org.jfrog.test.gradle.shared:moda:1.0-SNAPSHOT").nodes().get(COMMONS_TEXT); + assertTrue(modaCommonsText.getChildren().contains(COMMONS_LANG3)); + assertTrue(modaCommonsText.getScopes().contains("implementation")); + DepTreeNode modbCommonsText = modulesByRoot.get("org.jfrog.test.gradle.shared:modb:1.0-SNAPSHOT").nodes().get(COMMONS_TEXT); + assertFalse(modbCommonsText.getChildren().contains(COMMONS_LANG3), + "'modb' excludes commons-lang3, so its own tree must not contain the edge"); } private DepTree buildGradleDependencyTree(String projectPath) throws IOException { From cc7cce88e3ea050ceccf25e68031c2e728df05d6 Mon Sep 17 00:00:00 2001 From: Jordan Hury Date: Thu, 24 Sep 2026 11:36:51 +0300 Subject: [PATCH 07/10] Run the jf audit tests on a copy of the example project Since jf 2.106.0, jf audit matches its exclusion patterns against the absolute path of the working directory, so the default *test* pattern skips any project under src/test and the audit returns an empty SARIF. Copy the fixture to a temporary directory first. Co-Authored-By: Claude Opus 5.5 --- .../configuration/JfrogCliDriverTest.java | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/test/java/com/jfrog/ide/common/configuration/JfrogCliDriverTest.java b/src/test/java/com/jfrog/ide/common/configuration/JfrogCliDriverTest.java index 4fbe7ca1..995bbe2c 100644 --- a/src/test/java/com/jfrog/ide/common/configuration/JfrogCliDriverTest.java +++ b/src/test/java/com/jfrog/ide/common/configuration/JfrogCliDriverTest.java @@ -5,6 +5,7 @@ import com.jfrog.ide.common.nodes.subentities.Severity; import com.jfrog.ide.common.nodes.subentities.SourceCodeScanType; import com.jfrog.ide.common.parse.SarifParser; +import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.SystemUtils; import org.jfrog.build.api.util.NullLog; import org.jfrog.build.extractor.executor.CommandResults; @@ -46,6 +47,7 @@ public class JfrogCliDriverTest { private final String XRAY_URL = SERVER_URL + "xray/"; private String testServerId; private File tempDir; + private File exampleProjectCopy; private final SarifParser parser = new SarifParser(new NullLog()); private final Logger logger = LoggerFactory.getLogger(JfrogCliDriverTest.class); @@ -204,8 +206,8 @@ public void testAddServerConfig_withBadCredentials() { @Test public void testRunAudit_NpmProject() { try { - Path exampleProjectsFolder = Path.of("src/test/resources/example-projects/npm"); - CommandResults response = jfrogCliDriver.runCliAudit(exampleProjectsFolder.toFile(), + File exampleProject = copyExampleProject("npm"); + CommandResults response = jfrogCliDriver.runCliAudit(exampleProject, null, testServerId, testEnv); assertEquals(response.getExitValue(),0); logger.info("Audit debug logs: \n" + response.getErr()); @@ -229,8 +231,8 @@ public void testRunAudit_NpmProject() { public void testRunAudit_MultiMavenProject() { List projectsToCheck = new ArrayList<>(Arrays.asList("multi1", "multi2")); try { - Path exampleProjectsFolder = Path.of("src/test/resources/example-projects/maven-example"); - CommandResults response = jfrogCliDriver.runCliAudit(exampleProjectsFolder.toFile(), + File exampleProject = copyExampleProject("maven-example"); + CommandResults response = jfrogCliDriver.runCliAudit(exampleProject, projectsToCheck, testServerId, testEnv); assertEquals(response.getExitValue(), 0); logger.info("Audit debug logs: \n" + response.getErr()); @@ -250,6 +252,16 @@ public void testRunAudit_MultiMavenProject() { } } + /** + * Audits run on a copy of the example project, because jf audit skips a working directory whose absolute + * path matches its default exclusion patterns, and this repository keeps its fixtures under src/test. + */ + private File copyExampleProject(String projectName) throws IOException { + exampleProjectCopy = Files.createTempDirectory("ide-plugins-common-audit").toFile(); + FileUtils.copyDirectory(Path.of("src/test/resources/example-projects", projectName).toFile(), exampleProjectCopy); + return exampleProjectCopy; + } + private String createServerId() { return "ide-plugins-common-test-server-" + timeStampFormat.format(System.currentTimeMillis()); } @@ -257,14 +269,14 @@ private String createServerId() { @Test public void testRunAudit_WithExcludedPattern() { try { - Path exampleProjectsFolder = Path.of("src/test/resources/example-projects/maven-example"); + File exampleProject = copyExampleProject("maven-example"); AuditConfig config = new AuditConfig.Builder() .serverId(testServerId) .excludedPattern(new ArrayList<>(List.of("*multi3*"))) .serverId(testServerId) .envVars(testEnv) .build(); - CommandResults response = jfrogCliDriver.runCliAudit(exampleProjectsFolder.toFile(), config); + CommandResults response = jfrogCliDriver.runCliAudit(exampleProject, config); assertEquals(response.getExitValue(), 0); logger.info("Audit debug logs: \n" + response.getErr()); logger.info("Audit response: \n" + response.getRes()); @@ -285,6 +297,7 @@ public void testRunAudit_WithExcludedPattern() { @AfterMethod public void cleanUp(Method method) { + FileUtils.deleteQuietly(exampleProjectCopy); try { if (!TEST_NAME_TO_SKIP_CLI_DOWNLOAD.equals(method.getName())) { String[] serverConfigCmdArgs = {"config", "remove", testServerId, "--quiet"}; From 336efbdc2435c09226848e06cd5eff960d285bfe Mon Sep 17 00:00:00 2001 From: Jordan Hury Date: Thu, 24 Sep 2026 11:54:53 +0300 Subject: [PATCH 08/10] Skip SARIF runs from scanners the parser does not support jf audit now also reports a JFrog Services scanner run. SourceCodeScanType has no value for it, so the whole SARIF failed to parse. Skip unsupported runs instead, and make the npm audit test assert on its SCA finding rather than on the number of files with findings, which grows with the scanners the server runs. Co-Authored-By: Claude Opus 5.5 --- src/main/java/com/jfrog/ide/common/parse/SarifParser.java | 8 +++++++- .../ide/common/configuration/JfrogCliDriverTest.java | 4 ++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/jfrog/ide/common/parse/SarifParser.java b/src/main/java/com/jfrog/ide/common/parse/SarifParser.java index 0f71154e..a47fff5d 100644 --- a/src/main/java/com/jfrog/ide/common/parse/SarifParser.java +++ b/src/main/java/com/jfrog/ide/common/parse/SarifParser.java @@ -67,7 +67,13 @@ private List parseScanFindings(List runs){ List resultsList = run.getResults(); // get the scanner tool name with characters only String sourceCodeToolName = run.getTool().getDriver().getName().replaceAll("[^a-zA-Z\\s]", "").trim(); - SourceCodeScanType reporter = SourceCodeScanType.fromParam(sourceCodeToolName); + SourceCodeScanType reporter; + try { + reporter = SourceCodeScanType.fromParam(sourceCodeToolName); + } catch (IllegalArgumentException e) { + log.debug("Skipping the results of an unsupported scanner: " + sourceCodeToolName); + continue; + } for (Result result : resultsList){ ReportingDescriptor rule = run.getTool().getDriver().getRules().stream() diff --git a/src/test/java/com/jfrog/ide/common/configuration/JfrogCliDriverTest.java b/src/test/java/com/jfrog/ide/common/configuration/JfrogCliDriverTest.java index 995bbe2c..dcc8cee9 100644 --- a/src/test/java/com/jfrog/ide/common/configuration/JfrogCliDriverTest.java +++ b/src/test/java/com/jfrog/ide/common/configuration/JfrogCliDriverTest.java @@ -216,8 +216,8 @@ public void testRunAudit_NpmProject() { assertNotNull(findings); assertFalse(findings.isEmpty(), "Expected findings in SARIF output for npm project"); // Verify the findings - assertEquals(findings.size(), 1, "Expected exactly one file with findings"); - FileTreeNode node = findings.get(0); + FileTreeNode node = findings.stream().filter(finding -> finding.getSubtitle().equals("package.json")).findFirst().orElse(null); + assertNotNull(node, "Expected SCA findings in package.json"); assertEquals(node.getChildren().size(), 1, "Expected exactly one vulnerabilities"); FileIssueNode issue = (FileIssueNode) node.getChildren().get(0); assertEquals(issue.getSeverity(), Severity.High, "Expected severity to be HIGH"); From 3f61a89efaa69166c75e5f4b86b699d0143e4da1 Mon Sep 17 00:00:00 2001 From: Jordan Hury Date: Thu, 24 Sep 2026 13:31:35 +0300 Subject: [PATCH 09/10] Warn when an unsupported scanner's results are dropped Co-Authored-By: Claude Opus 5.5 --- src/main/java/com/jfrog/ide/common/parse/SarifParser.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/jfrog/ide/common/parse/SarifParser.java b/src/main/java/com/jfrog/ide/common/parse/SarifParser.java index a47fff5d..7ec3872a 100644 --- a/src/main/java/com/jfrog/ide/common/parse/SarifParser.java +++ b/src/main/java/com/jfrog/ide/common/parse/SarifParser.java @@ -71,7 +71,11 @@ private List parseScanFindings(List runs){ try { reporter = SourceCodeScanType.fromParam(sourceCodeToolName); } catch (IllegalArgumentException e) { - log.debug("Skipping the results of an unsupported scanner: " + sourceCodeToolName); + if (resultsList.isEmpty()) { + log.debug("Skipping an unsupported scanner with no results: " + sourceCodeToolName); + } else { + log.warn("Skipping " + resultsList.size() + " results of an unsupported scanner: " + sourceCodeToolName); + } continue; } From 9dd03034115fa3a18a1b6a77dd83db6ff230d245 Mon Sep 17 00:00:00 2001 From: Jordan Hury Date: Sun, 27 Sep 2026 13:34:46 +0300 Subject: [PATCH 10/10] Address review of the Gradle merge and audit test changes Treat a missing results list like an empty one when skipping an unsupported scanner, and cover the skip with a SARIF fixture holding an SCA run plus unsupported runs with no, some and missing results. The audit tests look up their SCA finding by file name instead of counting files, and the shared-dependency test asserts each node and module exists before reading it. Co-Authored-By: Claude Opus 5.5 --- .../jfrog/ide/common/parse/SarifParser.java | 2 +- .../configuration/JfrogCliDriverTest.java | 12 +- .../common/gradle/GradleTreeBuilderTest.java | 14 +- .../ide/common/parse/SarifParserTest.java | 8 + .../parse/sca_and_unsupported_scanners.json | 713 ++++++++++++++++++ 5 files changed, 739 insertions(+), 10 deletions(-) create mode 100644 src/test/resources/parse/sca_and_unsupported_scanners.json diff --git a/src/main/java/com/jfrog/ide/common/parse/SarifParser.java b/src/main/java/com/jfrog/ide/common/parse/SarifParser.java index 7ec3872a..bc4c6594 100644 --- a/src/main/java/com/jfrog/ide/common/parse/SarifParser.java +++ b/src/main/java/com/jfrog/ide/common/parse/SarifParser.java @@ -71,7 +71,7 @@ private List parseScanFindings(List runs){ try { reporter = SourceCodeScanType.fromParam(sourceCodeToolName); } catch (IllegalArgumentException e) { - if (resultsList.isEmpty()) { + if (resultsList == null || resultsList.isEmpty()) { log.debug("Skipping an unsupported scanner with no results: " + sourceCodeToolName); } else { log.warn("Skipping " + resultsList.size() + " results of an unsupported scanner: " + sourceCodeToolName); diff --git a/src/test/java/com/jfrog/ide/common/configuration/JfrogCliDriverTest.java b/src/test/java/com/jfrog/ide/common/configuration/JfrogCliDriverTest.java index dcc8cee9..b46d03ee 100644 --- a/src/test/java/com/jfrog/ide/common/configuration/JfrogCliDriverTest.java +++ b/src/test/java/com/jfrog/ide/common/configuration/JfrogCliDriverTest.java @@ -216,7 +216,7 @@ public void testRunAudit_NpmProject() { assertNotNull(findings); assertFalse(findings.isEmpty(), "Expected findings in SARIF output for npm project"); // Verify the findings - FileTreeNode node = findings.stream().filter(finding -> finding.getSubtitle().equals("package.json")).findFirst().orElse(null); + FileTreeNode node = findings.stream().filter(finding -> finding.getTitle().equals("package.json")).findFirst().orElse(null); assertNotNull(node, "Expected SCA findings in package.json"); assertEquals(node.getChildren().size(), 1, "Expected exactly one vulnerabilities"); FileIssueNode issue = (FileIssueNode) node.getChildren().get(0); @@ -241,8 +241,8 @@ public void testRunAudit_MultiMavenProject() { assertNotNull(findings); assertFalse(findings.isEmpty(), "Expected findings in SARIF output for multi-maven project"); // Verify the findings - assertEquals(findings.size(), 1, "Expected exactly one file with findings"); - FileTreeNode node = findings.get(0); + FileTreeNode node = findings.stream().filter(finding -> finding.getTitle().equals("pom.xml")).findFirst().orElse(null); + assertNotNull(node, "Expected SCA findings in pom.xml"); assertEquals(node.getChildren().size(), 3, "Expected exactly three vulnerabilities"); assertEquals(node.getSeverity(), Severity.High, "Expected severity to be HIGH"); FileIssueNode issue = (FileIssueNode) node.getChildren().get(0); @@ -262,7 +262,7 @@ private File copyExampleProject(String projectName) throws IOException { return exampleProjectCopy; } - private String createServerId() { + private String createServerId() { return "ide-plugins-common-test-server-" + timeStampFormat.format(System.currentTimeMillis()); } @@ -284,8 +284,8 @@ public void testRunAudit_WithExcludedPattern() { assertNotNull(findings); assertFalse(findings.isEmpty(), "Expected findings in SARIF output for multi-maven project"); // Verify the findings - assertEquals(findings.size(), 1, "Expected exactly one file with findings"); - FileTreeNode node = findings.get(0); + FileTreeNode node = findings.stream().filter(finding -> finding.getTitle().equals("pom.xml")).findFirst().orElse(null); + assertNotNull(node, "Expected SCA findings in pom.xml"); assertEquals(node.getChildren().size(), 3, "Expected exactly three vulnerabilities"); assertEquals(node.getSeverity(), Severity.High, "Expected severity to be HIGH"); FileIssueNode issue = (FileIssueNode) node.getChildren().get(0); diff --git a/src/test/java/com/jfrog/ide/common/gradle/GradleTreeBuilderTest.java b/src/test/java/com/jfrog/ide/common/gradle/GradleTreeBuilderTest.java index 0aa1d199..587e9210 100644 --- a/src/test/java/com/jfrog/ide/common/gradle/GradleTreeBuilderTest.java +++ b/src/test/java/com/jfrog/ide/common/gradle/GradleTreeBuilderTest.java @@ -99,15 +99,23 @@ public void gradleTreeBuilderSharedDependencyTest(String projectPath) throws IOE final String COMMONS_LANG3 = "org.apache.commons:commons-lang3:3.11"; DepTree depTree = buildGradleDependencyTree(projectPath); - assertTrue(depTree.nodes().get(COMMONS_TEXT).getChildren().contains(COMMONS_LANG3), + DepTreeNode commonsText = depTree.nodes().get(COMMONS_TEXT); + assertNotNull(commonsText, "Couldn't find node '" + COMMONS_TEXT + "'."); + assertTrue(commonsText.getChildren().contains(COMMONS_LANG3), "The merged tree must keep the edge resolved by 'moda'"); Map modulesByRoot = new HashMap<>(); depTree.modules().forEach(module -> modulesByRoot.put(module.rootId(), module)); - DepTreeNode modaCommonsText = modulesByRoot.get("org.jfrog.test.gradle.shared:moda:1.0-SNAPSHOT").nodes().get(COMMONS_TEXT); + DepTreeModule moda = modulesByRoot.get("org.jfrog.test.gradle.shared:moda:1.0-SNAPSHOT"); + assertNotNull(moda, "Couldn't find the 'moda' module tree in " + modulesByRoot.keySet()); + DepTreeNode modaCommonsText = moda.nodes().get(COMMONS_TEXT); + assertNotNull(modaCommonsText, "Couldn't find '" + COMMONS_TEXT + "' in the 'moda' module tree."); assertTrue(modaCommonsText.getChildren().contains(COMMONS_LANG3)); assertTrue(modaCommonsText.getScopes().contains("implementation")); - DepTreeNode modbCommonsText = modulesByRoot.get("org.jfrog.test.gradle.shared:modb:1.0-SNAPSHOT").nodes().get(COMMONS_TEXT); + DepTreeModule modb = modulesByRoot.get("org.jfrog.test.gradle.shared:modb:1.0-SNAPSHOT"); + assertNotNull(modb, "Couldn't find the 'modb' module tree in " + modulesByRoot.keySet()); + DepTreeNode modbCommonsText = modb.nodes().get(COMMONS_TEXT); + assertNotNull(modbCommonsText, "Couldn't find '" + COMMONS_TEXT + "' in the 'modb' module tree."); assertFalse(modbCommonsText.getChildren().contains(COMMONS_LANG3), "'modb' excludes commons-lang3, so its own tree must not contain the edge"); } diff --git a/src/test/java/com/jfrog/ide/common/parse/SarifParserTest.java b/src/test/java/com/jfrog/ide/common/parse/SarifParserTest.java index f55449a2..91f03505 100644 --- a/src/test/java/com/jfrog/ide/common/parse/SarifParserTest.java +++ b/src/test/java/com/jfrog/ide/common/parse/SarifParserTest.java @@ -46,6 +46,14 @@ public void testParseInvalidSarifReport() { assertThrows(NullPointerException.class, () -> parser.parse(readSarifReportFromFile(resourcesDir + "invalid_sarif_no_results.json"))); } + @Test + public void testParseSarifReportSkipsUnsupportedScanners() throws IOException { + results = parser.parse(readSarifReportFromFile(resourcesDir + "sca_and_unsupported_scanners.json")); + + assertEquals(results.size(), 1); + assertEquals(results.get(0).getChildren().size(), 10); + } + @Test public void testParseSarifReportWithOnlyScaResults() throws IOException { results = parser.parse(readSarifReportFromFile(resourcesDir + "sca_no_jas.json")); diff --git a/src/test/resources/parse/sca_and_unsupported_scanners.json b/src/test/resources/parse/sca_and_unsupported_scanners.json new file mode 100644 index 00000000..83728438 --- /dev/null +++ b/src/test/resources/parse/sca_and_unsupported_scanners.json @@ -0,0 +1,713 @@ +{ + "version": "2.1.0", + "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json", + "runs": [ + { + "tool": { + "driver": { + "informationUri": "https://docs.jfrog-applications.jfrog.io/jfrog-security-features/sca", + "name": "JFrog Xray Scanner", + "rules": [ + { + "id": "CVE-2024-30172_BouncyCastle_1.8.9", + "name": "CVE-2024-30172-BouncyCastle-1.8.9", + "shortDescription": { + "text": "[CVE-2024-30172] BouncyCastle 1.8.9" + }, + "fullDescription": { + "text": "An issue was discovered in Bouncy Castle Java Cryptography APIs before 1.78. An Ed25519 verification code infinite loop can occur via a crafted signature and public key.", + "markdown": "| Severity Score | Contextual Analysis | Direct Dependencies | Fixed Versions |\n| :---: | :---: | :---: | :---: |\n| 5.3 | Not Covered | `BouncyCastle 1.8.9` | No fix available |" + }, + "help": { + "text": "An issue was discovered in Bouncy Castle Java Cryptography APIs before 1.78. An Ed25519 verification code infinite loop can occur via a crafted signature and public key.", + "markdown": "| Severity Score | Contextual Analysis | Direct Dependencies | Fixed Versions |\n| :---: | :---: | :---: | :---: |\n| 5.3 | Not Covered | `BouncyCastle 1.8.9` | No fix available |" + }, + "properties": { + "impactPaths": [ + [ + { + "name": "TestApp1", + "version": "" + }, + { + "name": "BouncyCastle", + "version": "1.8.9" + } + ] + ], + "security-severity": "5.3" + } + }, + { + "id": "CVE-2024-30171_BouncyCastle_1.8.9", + "name": "CVE-2024-30171-BouncyCastle-1.8.9", + "shortDescription": { + "text": "[CVE-2024-30171] BouncyCastle 1.8.9" + }, + "fullDescription": { + "text": "An issue was discovered in Bouncy Castle Java TLS API and JSSE Provider before 1.78. Timing-based leakage may occur in RSA based handshakes because of exception processing.", + "markdown": "| Severity Score | Contextual Analysis | Direct Dependencies | Fixed Versions |\n| :---: | :---: | :---: | :---: |\n| 5.9 | Not Covered | `BouncyCastle 1.8.9` | No fix available |" + }, + "help": { + "text": "An issue was discovered in Bouncy Castle Java TLS API and JSSE Provider before 1.78. Timing-based leakage may occur in RSA based handshakes because of exception processing.", + "markdown": "| Severity Score | Contextual Analysis | Direct Dependencies | Fixed Versions |\n| :---: | :---: | :---: | :---: |\n| 5.9 | Not Covered | `BouncyCastle 1.8.9` | No fix available |" + }, + "properties": { + "impactPaths": [ + [ + { + "name": "TestApp1", + "version": "" + }, + { + "name": "BouncyCastle", + "version": "1.8.9" + } + ] + ], + "security-severity": "5.9" + } + }, + { + "id": "CVE-2020-1045_Microsoft.AspNetCore.Http_2.1.0", + "name": "CVE-2020-1045-Microsoft.AspNetCore.Http-2.1.0", + "shortDescription": { + "text": "[CVE-2020-1045] Microsoft.AspNetCore.Http 2.1.0" + }, + "fullDescription": { + "text": "

A security feature bypass vulnerability exists in the way Microsoft ASP.NET Core parses encoded cookie names.

\n

The ASP.NET Core cookie parser decodes entire cookie strings which could allow a malicious attacker to set a second cookie with the name being percent encoded.

\n

The security update addresses the vulnerability by fixing the way the ASP.NET Core cookie parser handles encoded names.

", + "markdown": "| Severity Score | Contextual Analysis | Direct Dependencies | Fixed Versions |\n| :---: | :---: | :---: | :---: |\n| 7.5 | Not Applicable | `Microsoft.AspNetCore.Hosting 2.1.0` | [2.1.22] |" + }, + "help": { + "text": "

A security feature bypass vulnerability exists in the way Microsoft ASP.NET Core parses encoded cookie names.

\n

The ASP.NET Core cookie parser decodes entire cookie strings which could allow a malicious attacker to set a second cookie with the name being percent encoded.

\n

The security update addresses the vulnerability by fixing the way the ASP.NET Core cookie parser handles encoded names.

", + "markdown": "| Severity Score | Contextual Analysis | Direct Dependencies | Fixed Versions |\n| :---: | :---: | :---: | :---: |\n| 7.5 | Not Applicable | `Microsoft.AspNetCore.Hosting 2.1.0` | [2.1.22] |" + }, + "properties": { + "impactPaths": [ + [ + { + "name": "ClassLibrary1", + "version": "" + }, + { + "name": "Microsoft.AspNetCore.Hosting", + "version": "2.1.0" + }, + { + "name": "Microsoft.AspNetCore.Http", + "version": "2.1.0" + } + ] + ], + "security-severity": "7.5" + } + }, + { + "id": "CVE-2020-1045_Microsoft.AspNetCore.Http_2.1.1", + "name": "CVE-2020-1045-Microsoft.AspNetCore.Http-2.1.1", + "shortDescription": { + "text": "[CVE-2020-1045] Microsoft.AspNetCore.Http 2.1.1" + }, + "fullDescription": { + "text": "

A security feature bypass vulnerability exists in the way Microsoft ASP.NET Core parses encoded cookie names.

\n

The ASP.NET Core cookie parser decodes entire cookie strings which could allow a malicious attacker to set a second cookie with the name being percent encoded.

\n

The security update addresses the vulnerability by fixing the way the ASP.NET Core cookie parser handles encoded names.

", + "markdown": "| Severity Score | Contextual Analysis | Direct Dependencies | Fixed Versions |\n| :---: | :---: | :---: | :---: |\n| 7.5 | Not Applicable | `Microsoft.AspNetCore.Hosting 2.1.1` | [2.1.22] |" + }, + "help": { + "text": "

A security feature bypass vulnerability exists in the way Microsoft ASP.NET Core parses encoded cookie names.

\n

The ASP.NET Core cookie parser decodes entire cookie strings which could allow a malicious attacker to set a second cookie with the name being percent encoded.

\n

The security update addresses the vulnerability by fixing the way the ASP.NET Core cookie parser handles encoded names.

", + "markdown": "| Severity Score | Contextual Analysis | Direct Dependencies | Fixed Versions |\n| :---: | :---: | :---: | :---: |\n| 7.5 | Not Applicable | `Microsoft.AspNetCore.Hosting 2.1.1` | [2.1.22] |" + }, + "properties": { + "impactPaths": [ + [ + { + "name": "TestApp1", + "version": "" + }, + { + "name": "Microsoft.AspNetCore.Hosting", + "version": "2.1.1" + }, + { + "name": "Microsoft.AspNetCore.Http", + "version": "2.1.1" + } + ] + ], + "security-severity": "7.5" + } + }, + { + "id": "CVE-2021-26701_System.Text.Encodings.Web_4.5.0", + "name": "CVE-2021-26701-System.Text.Encodings.Web-4.5.0", + "shortDescription": { + "text": "[CVE-2021-26701] System.Text.Encodings.Web 4.5.0" + }, + "fullDescription": { + "text": ".NET Core Remote Code Execution Vulnerability", + "markdown": "| Severity Score | Contextual Analysis | Direct Dependencies | Fixed Versions |\n| :---: | :---: | :---: | :---: |\n| 8.1 | Not Applicable | `Microsoft.AspNetCore.Hosting 2.1.0`
`Serilog.AspNetCore 2.1.1` | [4.5.1], [4.7.2], [5.0.1] |" + }, + "help": { + "text": ".NET Core Remote Code Execution Vulnerability", + "markdown": "| Severity Score | Contextual Analysis | Direct Dependencies | Fixed Versions |\n| :---: | :---: | :---: | :---: |\n| 8.1 | Not Applicable | `Microsoft.AspNetCore.Hosting 2.1.0`
`Serilog.AspNetCore 2.1.1` | [4.5.1], [4.7.2], [5.0.1] |" + }, + "properties": { + "impactPaths": [ + [ + { + "name": "TestApp1", + "version": "" + }, + { + "name": "Serilog.AspNetCore", + "version": "2.1.1" + }, + { + "name": "Microsoft.AspNetCore.Hosting.Abstractions", + "version": "2.1.1" + }, + { + "name": "Microsoft.AspNetCore.Http.Abstractions", + "version": "2.1.1" + }, + { + "name": "System.Text.Encodings.Web", + "version": "4.5.0" + } + ], + [ + { + "name": "ClassLibrary1", + "version": "" + }, + { + "name": "Microsoft.AspNetCore.Hosting", + "version": "2.1.0" + }, + { + "name": "Microsoft.AspNetCore.Http", + "version": "2.1.0" + }, + { + "name": "Microsoft.AspNetCore.WebUtilities", + "version": "2.1.0" + }, + { + "name": "System.Text.Encodings.Web", + "version": "4.5.0" + } + ] + ], + "security-severity": "8.1" + } + }, + { + "id": "CVE-2024-21907_Newtonsoft.Json_12.0.3", + "name": "CVE-2024-21907-Newtonsoft.Json-12.0.3", + "shortDescription": { + "text": "[CVE-2024-21907] Newtonsoft.Json 12.0.3" + }, + "fullDescription": { + "text": "Newtonsoft.Json before version 13.0.1 is affected by a mishandling of exceptional conditions vulnerability. Crafted data that is passed to the JsonConvert.DeserializeObject method may trigger a StackOverflow exception resulting in denial of service. Depending on the usage of the library, an unauthenticated and remote attacker may be able to cause the denial of service condition.", + "markdown": "| Severity Score | Contextual Analysis | Direct Dependencies | Fixed Versions |\n| :---: | :---: | :---: | :---: |\n| 7.5 | Not Applicable | `Newtonsoft.Json 12.0.3`
`Google.Cloud.Kms.V1 2.0.0` | [13.0.1] |" + }, + "help": { + "text": "Newtonsoft.Json before version 13.0.1 is affected by a mishandling of exceptional conditions vulnerability. Crafted data that is passed to the JsonConvert.DeserializeObject method may trigger a StackOverflow exception resulting in denial of service. Depending on the usage of the library, an unauthenticated and remote attacker may be able to cause the denial of service condition.", + "markdown": "| Severity Score | Contextual Analysis | Direct Dependencies | Fixed Versions |\n| :---: | :---: | :---: | :---: |\n| 7.5 | Not Applicable | `Newtonsoft.Json 12.0.3`
`Google.Cloud.Kms.V1 2.0.0` | [13.0.1] |" + }, + "properties": { + "impactPaths": [ + [ + { + "name": "TestApp1", + "version": "" + }, + { + "name": "Google.Cloud.Kms.V1", + "version": "2.0.0" + }, + { + "name": "Google.Api.Gax.Grpc.GrpcCore", + "version": "3.0.0" + }, + { + "name": "Google.Api.Gax.Grpc", + "version": "3.0.0" + }, + { + "name": "Google.Api.Gax", + "version": "3.0.0" + }, + { + "name": "Newtonsoft.Json", + "version": "12.0.3" + } + ], + [ + { + "name": "ClassLibrary1", + "version": "" + }, + { + "name": "Newtonsoft.Json", + "version": "12.0.3" + } + ] + ], + "security-severity": "7.5" + } + }, + { + "id": "CVE-2021-22570_Google.Protobuf_3.11.4", + "name": "CVE-2021-22570-Google.Protobuf-3.11.4", + "shortDescription": { + "text": "[CVE-2021-22570] Google.Protobuf 3.11.4" + }, + "fullDescription": { + "text": "Nullptr dereference when a null char is present in a proto symbol. The symbol is parsed incorrectly, leading to an unchecked call into the proto file's name during generation of the resulting error message. Since the symbol is incorrectly parsed, the file is nullptr. We recommend upgrading to version 3.15.0 or greater.", + "markdown": "| Severity Score | Contextual Analysis | Direct Dependencies | Fixed Versions |\n| :---: | :---: | :---: | :---: |\n| 5.5 | Not Covered | `Google.Cloud.Kms.V1 2.0.0` | [3.15.0] |" + }, + "help": { + "text": "Nullptr dereference when a null char is present in a proto symbol. The symbol is parsed incorrectly, leading to an unchecked call into the proto file's name during generation of the resulting error message. Since the symbol is incorrectly parsed, the file is nullptr. We recommend upgrading to version 3.15.0 or greater.", + "markdown": "| Severity Score | Contextual Analysis | Direct Dependencies | Fixed Versions |\n| :---: | :---: | :---: | :---: |\n| 5.5 | Not Covered | `Google.Cloud.Kms.V1 2.0.0` | [3.15.0] |" + }, + "properties": { + "impactPaths": [ + [ + { + "name": "TestApp1", + "version": "" + }, + { + "name": "Google.Cloud.Kms.V1", + "version": "2.0.0" + }, + { + "name": "Google.Api.Gax.Grpc.GrpcCore", + "version": "3.0.0" + }, + { + "name": "Google.Api.Gax.Grpc", + "version": "3.0.0" + }, + { + "name": "Google.Api.CommonProtos", + "version": "2.0.0" + }, + { + "name": "Google.Protobuf", + "version": "3.11.4" + } + ] + ], + "security-severity": "5.5" + } + }, + { + "id": "CVE-2024-29857_BouncyCastle_1.8.9", + "name": "CVE-2024-29857-BouncyCastle-1.8.9", + "shortDescription": { + "text": "[CVE-2024-29857] BouncyCastle 1.8.9" + }, + "fullDescription": { + "text": "An issue was discovered in ECCurve.java and ECCurve.cs in Bouncy Castle Java (BC Java) before 1.78, BC Java LTS before 2.73.6, BC-FJA before 1.0.2.5, and BC C# .Net before 2.3.1. Importing an EC certificate with crafted F2m parameters can lead to excessive CPU consumption during the evaluation of the curve parameters.", + "markdown": "| Severity Score | Contextual Analysis | Direct Dependencies | Fixed Versions |\n| :---: | :---: | :---: | :---: |\n| 5.3 | Not Covered | `BouncyCastle 1.8.9` | No fix available |" + }, + "help": { + "text": "An issue was discovered in ECCurve.java and ECCurve.cs in Bouncy Castle Java (BC Java) before 1.78, BC Java LTS before 2.73.6, BC-FJA before 1.0.2.5, and BC C# .Net before 2.3.1. Importing an EC certificate with crafted F2m parameters can lead to excessive CPU consumption during the evaluation of the curve parameters.", + "markdown": "| Severity Score | Contextual Analysis | Direct Dependencies | Fixed Versions |\n| :---: | :---: | :---: | :---: |\n| 5.3 | Not Covered | `BouncyCastle 1.8.9` | No fix available |" + }, + "properties": { + "impactPaths": [ + [ + { + "name": "TestApp1", + "version": "" + }, + { + "name": "BouncyCastle", + "version": "1.8.9" + } + ] + ], + "security-severity": "5.3" + } + } + ], + "version": "3.115.6" + } + }, + "invocations": [ + { + "executionSuccessful": true, + "workingDirectory": { + "uri": "C:\\Users\\User\\Desktop\\nuget\\multi" + } + } + ], + "results": [ + { + "properties": { + "applicability": "Not Applicable", + "fixedVersion": "[2.1.22]" + }, + "ruleId": "CVE-2020-1045_Microsoft.AspNetCore.Http_2.1.0", + "ruleIndex": 2, + "level": "error", + "message": { + "text": "[CVE-2020-1045] Microsoft.AspNetCore.Hosting 2.1.0", + "markdown": "[CVE-2020-1045] Microsoft.AspNetCore.Hosting 2.1.0" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "TestSolution.sln" + } + } + } + ] + }, + { + "properties": { + "applicability": "Not Applicable", + "fixedVersion": "[2.1.22]" + }, + "ruleId": "CVE-2020-1045_Microsoft.AspNetCore.Http_2.1.1", + "ruleIndex": 3, + "level": "error", + "message": { + "text": "[CVE-2020-1045] Microsoft.AspNetCore.Hosting 2.1.1", + "markdown": "[CVE-2020-1045] Microsoft.AspNetCore.Hosting 2.1.1" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "TestSolution.sln" + } + } + } + ] + }, + { + "properties": { + "applicability": "Not Applicable", + "fixedVersion": "[4.5.1], [4.7.2], [5.0.1]" + }, + "ruleId": "CVE-2021-26701_System.Text.Encodings.Web_4.5.0", + "ruleIndex": 4, + "level": "error", + "message": { + "text": "[CVE-2021-26701] Microsoft.AspNetCore.Hosting 2.1.0", + "markdown": "[CVE-2021-26701] Microsoft.AspNetCore.Hosting 2.1.0" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "TestSolution.sln" + } + } + } + ] + }, + { + "properties": { + "applicability": "Not Applicable", + "fixedVersion": "[4.5.1], [4.7.2], [5.0.1]" + }, + "ruleId": "CVE-2021-26701_System.Text.Encodings.Web_4.5.0", + "ruleIndex": 4, + "level": "error", + "message": { + "text": "[CVE-2021-26701] Serilog.AspNetCore 2.1.1", + "markdown": "[CVE-2021-26701] Serilog.AspNetCore 2.1.1" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "TestSolution.sln" + } + } + } + ] + }, + { + "properties": { + "applicability": "Not Applicable", + "fixedVersion": "[13.0.1]" + }, + "ruleId": "CVE-2024-21907_Newtonsoft.Json_12.0.3", + "ruleIndex": 5, + "level": "error", + "message": { + "text": "[CVE-2024-21907] Newtonsoft.Json 12.0.3", + "markdown": "[CVE-2024-21907] Newtonsoft.Json 12.0.3" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "TestSolution.sln" + } + } + } + ] + }, + { + "properties": { + "applicability": "Not Applicable", + "fixedVersion": "[13.0.1]" + }, + "ruleId": "CVE-2024-21907_Newtonsoft.Json_12.0.3", + "ruleIndex": 5, + "level": "error", + "message": { + "text": "[CVE-2024-21907] Google.Cloud.Kms.V1 2.0.0", + "markdown": "[CVE-2024-21907] Google.Cloud.Kms.V1 2.0.0" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "TestSolution.sln" + } + } + } + ] + }, + { + "properties": { + "applicability": "Not Covered", + "fixedVersion": "[3.15.0]" + }, + "ruleId": "CVE-2021-22570_Google.Protobuf_3.11.4", + "ruleIndex": 6, + "level": "warning", + "message": { + "text": "[CVE-2021-22570] Google.Cloud.Kms.V1 2.0.0", + "markdown": "[CVE-2021-22570] Google.Cloud.Kms.V1 2.0.0" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "TestSolution.sln" + } + } + } + ] + }, + { + "properties": { + "applicability": "Not Covered", + "fixedVersion": "No fix available" + }, + "ruleId": "CVE-2024-29857_BouncyCastle_1.8.9", + "ruleIndex": 7, + "level": "warning", + "message": { + "text": "[CVE-2024-29857] BouncyCastle 1.8.9", + "markdown": "[CVE-2024-29857] BouncyCastle 1.8.9" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "TestSolution.sln" + } + } + } + ] + }, + { + "properties": { + "applicability": "Not Covered", + "fixedVersion": "No fix available" + }, + "ruleId": "CVE-2024-30172_BouncyCastle_1.8.9", + "ruleIndex": 0, + "level": "warning", + "message": { + "text": "[CVE-2024-30172] BouncyCastle 1.8.9", + "markdown": "[CVE-2024-30172] BouncyCastle 1.8.9" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "TestSolution.sln" + } + } + } + ] + }, + { + "properties": { + "applicability": "Not Covered", + "fixedVersion": "No fix available" + }, + "ruleId": "CVE-2024-30171_BouncyCastle_1.8.9", + "ruleIndex": 1, + "level": "warning", + "message": { + "text": "[CVE-2024-30171] BouncyCastle 1.8.9", + "markdown": "[CVE-2024-30171] BouncyCastle 1.8.9" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "TestSolution.sln" + } + } + } + ] + } + ] + }, + { + "tool": { + "driver": { + "name": "JFrog Services scanner", + "rules": [ + { + "id": "CVE-2024-30172_BouncyCastle_1.8.9", + "name": "CVE-2024-30172-BouncyCastle-1.8.9", + "shortDescription": { + "text": "[CVE-2024-30172] BouncyCastle 1.8.9" + }, + "fullDescription": { + "text": "An issue was discovered in Bouncy Castle Java Cryptography APIs before 1.78. An Ed25519 verification code infinite loop can occur via a crafted signature and public key.", + "markdown": "| Severity Score | Contextual Analysis | Direct Dependencies | Fixed Versions |\n| :---: | :---: | :---: | :---: |\n| 5.3 | Not Covered | `BouncyCastle 1.8.9` | No fix available |" + }, + "help": { + "text": "An issue was discovered in Bouncy Castle Java Cryptography APIs before 1.78. An Ed25519 verification code infinite loop can occur via a crafted signature and public key.", + "markdown": "| Severity Score | Contextual Analysis | Direct Dependencies | Fixed Versions |\n| :---: | :---: | :---: | :---: |\n| 5.3 | Not Covered | `BouncyCastle 1.8.9` | No fix available |" + }, + "properties": { + "impactPaths": [ + [ + { + "name": "TestApp1", + "version": "" + }, + { + "name": "BouncyCastle", + "version": "1.8.9" + } + ] + ], + "security-severity": "5.3" + } + } + ] + } + }, + "results": [] + }, + { + "tool": { + "driver": { + "name": "JFrog Services scanner", + "rules": [ + { + "id": "CVE-2024-30172_BouncyCastle_1.8.9", + "name": "CVE-2024-30172-BouncyCastle-1.8.9", + "shortDescription": { + "text": "[CVE-2024-30172] BouncyCastle 1.8.9" + }, + "fullDescription": { + "text": "An issue was discovered in Bouncy Castle Java Cryptography APIs before 1.78. An Ed25519 verification code infinite loop can occur via a crafted signature and public key.", + "markdown": "| Severity Score | Contextual Analysis | Direct Dependencies | Fixed Versions |\n| :---: | :---: | :---: | :---: |\n| 5.3 | Not Covered | `BouncyCastle 1.8.9` | No fix available |" + }, + "help": { + "text": "An issue was discovered in Bouncy Castle Java Cryptography APIs before 1.78. An Ed25519 verification code infinite loop can occur via a crafted signature and public key.", + "markdown": "| Severity Score | Contextual Analysis | Direct Dependencies | Fixed Versions |\n| :---: | :---: | :---: | :---: |\n| 5.3 | Not Covered | `BouncyCastle 1.8.9` | No fix available |" + }, + "properties": { + "impactPaths": [ + [ + { + "name": "TestApp1", + "version": "" + }, + { + "name": "BouncyCastle", + "version": "1.8.9" + } + ] + ], + "security-severity": "5.3" + } + } + ] + } + }, + "results": [ + { + "properties": { + "applicability": "Not Applicable", + "fixedVersion": "[2.1.22]" + }, + "ruleId": "CVE-2020-1045_Microsoft.AspNetCore.Http_2.1.0", + "ruleIndex": 2, + "level": "error", + "message": { + "text": "[CVE-2020-1045] Microsoft.AspNetCore.Hosting 2.1.0", + "markdown": "[CVE-2020-1045] Microsoft.AspNetCore.Hosting 2.1.0" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "TestSolution.sln" + } + } + } + ] + } + ] + }, + { + "tool": { + "driver": { + "name": "JFrog Services scanner", + "rules": [ + { + "id": "CVE-2024-30172_BouncyCastle_1.8.9", + "name": "CVE-2024-30172-BouncyCastle-1.8.9", + "shortDescription": { + "text": "[CVE-2024-30172] BouncyCastle 1.8.9" + }, + "fullDescription": { + "text": "An issue was discovered in Bouncy Castle Java Cryptography APIs before 1.78. An Ed25519 verification code infinite loop can occur via a crafted signature and public key.", + "markdown": "| Severity Score | Contextual Analysis | Direct Dependencies | Fixed Versions |\n| :---: | :---: | :---: | :---: |\n| 5.3 | Not Covered | `BouncyCastle 1.8.9` | No fix available |" + }, + "help": { + "text": "An issue was discovered in Bouncy Castle Java Cryptography APIs before 1.78. An Ed25519 verification code infinite loop can occur via a crafted signature and public key.", + "markdown": "| Severity Score | Contextual Analysis | Direct Dependencies | Fixed Versions |\n| :---: | :---: | :---: | :---: |\n| 5.3 | Not Covered | `BouncyCastle 1.8.9` | No fix available |" + }, + "properties": { + "impactPaths": [ + [ + { + "name": "TestApp1", + "version": "" + }, + { + "name": "BouncyCastle", + "version": "1.8.9" + } + ] + ], + "security-severity": "5.3" + } + } + ] + } + } + } + ] +} \ No newline at end of file