diff --git a/core/src/main/java/org/owasp/dependencycheck/analyzer/DependencyBundlingAnalyzer.java b/core/src/main/java/org/owasp/dependencycheck/analyzer/DependencyBundlingAnalyzer.java
index 7f16342a027..11d130354d9 100644
--- a/core/src/main/java/org/owasp/dependencycheck/analyzer/DependencyBundlingAnalyzer.java
+++ b/core/src/main/java/org/owasp/dependencycheck/analyzer/DependencyBundlingAnalyzer.java
@@ -211,6 +211,7 @@ public static void mergeDependencies(final Dependency dependency,
// we may want to merge project references on virtual dependencies...
if (dependency.getSha1sum() != null && dependency.getSha1sum().equals(relatedDependency.getSha1sum())) {
dependency.addAllProjectReferences(relatedDependency.getProjectReferences());
+ dependency.addAllIncludedBy(relatedDependency.getIncludedBy());
}
if (dependenciesToRemove != null) {
dependenciesToRemove.add(relatedDependency);
diff --git a/core/src/main/java/org/owasp/dependencycheck/dependency/Dependency.java b/core/src/main/java/org/owasp/dependencycheck/dependency/Dependency.java
index 0d5393c1f00..92a1402ebd2 100644
--- a/core/src/main/java/org/owasp/dependencycheck/dependency/Dependency.java
+++ b/core/src/main/java/org/owasp/dependencycheck/dependency/Dependency.java
@@ -102,6 +102,13 @@ public class Dependency extends EvidenceCollection implements Serializable {
* A collection of related dependencies.
*/
private final SortedSet relatedDependencies = new TreeSet<>(Dependency.NAME_COMPARATOR);
+ /**
+ * The set of dependencies that included this dependency (i.e., this is a
+ * transitive dependency because it was included by X). This is a pair where
+ * the left element is the includedBy and the right element is the type
+ * (e.g. buildEnv, plugins).
+ */
+ private final Set includedBy = new HashSet<>();
/**
* A list of projects that reference this dependency.
*/
@@ -784,6 +791,46 @@ public synchronized void clearRelatedDependencies() {
relatedDependencies.clear();
}
+ /**
+ * Get the unmodifiable set of includedBy (the list of parents of this
+ * transitive dependency).
+ *
+ * @return the unmodifiable set of includedBy
+ */
+ public synchronized Set getIncludedBy() {
+ return Collections.unmodifiableSet(new HashSet<>(includedBy));
+ }
+
+ /**
+ * Adds the parent or root of the transitive dependency chain (i.e., this
+ * was included by the parent dependency X).
+ *
+ * @param includedBy a project reference
+ */
+ public synchronized void addIncludedBy(String includedBy) {
+ this.includedBy.add(new IncludedByReference(includedBy, null));
+ }
+
+ /**
+ * Adds the parent or root of the transitive dependency chain (i.e., this
+ * was included by the parent dependency X).
+ *
+ * @param includedBy a project reference
+ * @param type the type of project reference (i.e. 'plugins', 'buildEnv')
+ */
+ public synchronized void addIncludedBy(String includedBy, String type) {
+ this.includedBy.add(new IncludedByReference(includedBy, type));
+ }
+
+ /**
+ * Adds a set of project references.
+ *
+ * @param includedBy a set of project references
+ */
+ public synchronized void addAllIncludedBy(Set includedBy) {
+ this.includedBy.addAll(includedBy);
+ }
+
/**
* Get the unmodifiable set of projectReferences.
*
diff --git a/core/src/main/java/org/owasp/dependencycheck/dependency/IncludedByReference.java b/core/src/main/java/org/owasp/dependencycheck/dependency/IncludedByReference.java
new file mode 100644
index 00000000000..bc8d70523e7
--- /dev/null
+++ b/core/src/main/java/org/owasp/dependencycheck/dependency/IncludedByReference.java
@@ -0,0 +1,73 @@
+/*
+ * This file is part of dependency-check-core.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * Copyright (c) 2023 Jeremy Long. All Rights Reserved.
+ */
+package org.owasp.dependencycheck.dependency;
+
+import java.io.Serializable;
+
+/**
+ * POJO to store a reference to the "included by" node in a dependency tree;
+ * where included by is the root node that caused a dependency to be included.
+ *
+ * @author Jeremy Long
+ */
+public class IncludedByReference implements Serializable {
+
+ /**
+ * The serial version UID for serialization.
+ */
+ private static final long serialVersionUID = 4339975160204621746L;
+
+ /**
+ * The reference.
+ */
+ private final String reference;
+ /**
+ * The reference's type.
+ */
+ private final String type;
+
+ /**
+ * Constructs a new reference.
+ *
+ * @param reference the reference
+ * @param type the reference's type
+ */
+ public IncludedByReference(String reference, String type) {
+ this.reference = reference;
+ this.type = type;
+ }
+
+ /**
+ * Get the value of reference.
+ *
+ * @return the value of reference
+ */
+ public String getReference() {
+ return reference;
+ }
+
+ /**
+ * Get the value of type.
+ *
+ * @return the value of type
+ */
+ public String getType() {
+ return type;
+ }
+
+}
diff --git a/core/src/main/resources/schema/dependency-check.2.5.xsd b/core/src/main/resources/schema/dependency-check.2.5.xsd
index 9e18c373499..f258e0661bc 100644
--- a/core/src/main/resources/schema/dependency-check.2.5.xsd
+++ b/core/src/main/resources/schema/dependency-check.2.5.xsd
@@ -195,6 +195,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/core/src/main/resources/templates/htmlReport.vsl b/core/src/main/resources/templates/htmlReport.vsl
index 76478d501d6..45587a2a6d9 100644
--- a/core/src/main/resources/templates/htmlReport.vsl
+++ b/core/src/main/resources/templates/htmlReport.vsl
@@ -605,6 +605,28 @@ Copyright (c) 2012 Jeremy Long. All Rights Reserved.
.underline {
text-decoration: underline;
}
+ .tooltip {
+ position: relative;
+ display: inline-block;
+ border-bottom: 1px dotted black;
+ }
+
+ .tooltip .tooltiptext {
+ visibility: hidden;
+ width: 220px;
+ background-color: #cccccc;
+ text-align: center;
+ border-radius: 6px;
+ padding: 5px 0;
+
+ /* Position the tooltip */
+ position: absolute;
+ z-index: 1;
+ }
+
+ .tooltip:hover .tooltiptext {
+ visibility: visible;
+ }
@@ -815,10 +837,7 @@ Getting Help: SHA256:$enc.html($dependency.Sha256sum)
#end
#if ($dependency.projectReferences.size()==1)
-
Referenced In Project/Scope:
- #foreach($ref in $dependency.projectReferences)
- $enc.html($ref)
- #end
+
Referenced In Project/Scope: $enc.html($dependency.projectReferences.iterator().next())
#end
#if ($dependency.projectReferences.size()>1)
Referenced In Projects/Scopes:
#set($cnt=$cnt+1)
Evidence
@@ -1033,11 +1063,32 @@ Getting Help: File Path: $enc.html($dependency.FilePath)
- #if(!$dependency.isVirtual())
+ #if(!$dependency.isVirtual())
MD5: $enc.html($dependency.Md5sum)
SHA1: $enc.html($dependency.Sha1sum)
SHA256: $enc.html($dependency.Sha256sum)
+ #end
+ #if ($dependency.projectReferences.size()==1)
+
Referenced In Project/Scope: $enc.html($dependency.projectReferences.iterator().next())
+ #end
+ #if ($dependency.projectReferences.size()>1)
+
Referenced In Projects/Scopes:
+ #foreach($ref in $dependency.projectReferences)
+ - $enc.html($ref)
+ #end
+
+ #end
+ #if ($dependency.includedBy.size()==1)
+ #set($incBy=$dependency.includedBy.iterator().next())
+
$enc.html($dependency.DisplayFileName) is in the transitive dependency tree of the listed items.Included by: $enc.html($incBy.getReference())#if($incBy.getType()) ($enc.html($incBy.getType()))#end
+ #end
+ #if ($dependency.includedBy.size()>1)
+
Included by:
+ #foreach($parent in $dependency.includedBy)
+ - $enc.html($parent.getReference())#if($parent.getType()) ($enc.html($parent.getType()))#end
#end
+
+ #end
#set($cnt=$cnt+1)
Evidence
diff --git a/core/src/main/resources/templates/jenkinsReport.vsl b/core/src/main/resources/templates/jenkinsReport.vsl
index f93b1655aeb..e38919ea98b 100644
--- a/core/src/main/resources/templates/jenkinsReport.vsl
+++ b/core/src/main/resources/templates/jenkinsReport.vsl
@@ -627,12 +627,13 @@ Getting Help:
#end
#if ($dependency.includedBy && $dependency.includedBy.size()==1)
-
$enc.html($dependency.DisplayFileName) is in the transitive dependency tree of the listed items.Included by: $enc.html($dependency.includedBy.iterator().next())
+ #set($incBy=$dependency.includedBy.iterator().next())
+
$enc.html($dependency.DisplayFileName) is in the transitive dependency tree of the listed items.Included by: $enc.html($incBy.getReference())#if($incBy.getType()) ($enc.html($incBy.getType()))#end
#end
#if ($dependency.includedBy && $dependency.includedBy.size()>1)
$enc.html($dependency.DisplayFileName) is in the transitive dependency tree of the listed items.Included by:
#foreach($parent in $dependency.includedBy)
- - $enc.html($parent)
+ - $enc.html($parent.getReference())#if($parent.getType()) ($enc.html($parent.getType()))#end
#end
#end
diff --git a/core/src/main/resources/templates/jsonReport.vsl b/core/src/main/resources/templates/jsonReport.vsl
index 28fac8ffc68..3e6634a5afc 100644
--- a/core/src/main/resources/templates/jsonReport.vsl
+++ b/core/src/main/resources/templates/jsonReport.vsl
@@ -70,6 +70,14 @@
#end
]
#end
+ #if ($dependency.includedBy.size()>0)
+ ,"includedBy": [
+ #foreach($ref in $dependency.includedBy)
+ #if($foreach.count > 1),#end
+ { "reference":"$enc.json($ref.getReference())"#if($ref.getType()),"type":"$enc.json($ref.getType())"#end }
+ #end
+ ]
+ #end
#if ($dependency.getRelatedDependencies().size()>0)
,"relatedDependencies": [
#foreach($related in $dependency.getRelatedDependencies()) #if($foreach.count > 1),#end {
diff --git a/core/src/main/resources/templates/xmlReport.vsl b/core/src/main/resources/templates/xmlReport.vsl
index c414c7820ef..b1e2018d645 100644
--- a/core/src/main/resources/templates/xmlReport.vsl
+++ b/core/src/main/resources/templates/xmlReport.vsl
@@ -89,6 +89,13 @@ Copyright (c) 2018 Jeremy Long. All Rights Reserved.
#end
#end
+#if ($dependency.includedBy.size()>0)
+
+#foreach($ref in $dependency.includedBy)
+ $enc.xml($ref.getReference())
+#end
+
+#end
#if ($dependency.getRelatedDependencies().size()>0)
#foreach($related in $dependency.getRelatedDependencies())
diff --git a/maven/src/main/java/org/owasp/dependencycheck/maven/AggregateMojo.java b/maven/src/main/java/org/owasp/dependencycheck/maven/AggregateMojo.java
index d7877a6484e..bc8bd135fcd 100644
--- a/maven/src/main/java/org/owasp/dependencycheck/maven/AggregateMojo.java
+++ b/maven/src/main/java/org/owasp/dependencycheck/maven/AggregateMojo.java
@@ -96,6 +96,24 @@ protected ExceptionCollection scanDependencies(final Engine engine) throws MojoE
return exCol;
}
+ /**
+ * Scans the plugins of the project.
+ *
+ * @param engine the engine used to perform the scanning
+ * @param exCollection the collection of exceptions that might have occurred
+ * previously
+ * @return a collection of exceptions
+ * @throws MojoExecutionException thrown if a fatal exception occurs
+ */
+ @Override
+ protected ExceptionCollection scanPlugins(final Engine engine, final ExceptionCollection exCollection) throws MojoExecutionException {
+ ExceptionCollection exCol = scanPlugins(getProject(), engine, null);
+ for (MavenProject childProject : getDescendants(this.getProject())) {
+ exCol = scanPlugins(childProject, engine, exCol);
+ }
+ return exCol;
+ }
+
/**
* Returns a set containing all the descendant projects of the given
* project.
diff --git a/maven/src/main/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojo.java b/maven/src/main/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojo.java
index e7dde01a8cb..52cd7de8462 100644
--- a/maven/src/main/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojo.java
+++ b/maven/src/main/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojo.java
@@ -19,6 +19,7 @@
import com.github.packageurl.MalformedPackageURLException;
import com.github.packageurl.PackageURL.StandardTypes;
+import com.github.packageurl.PackageURL;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.DefaultArtifact;
import org.apache.maven.artifact.handler.DefaultArtifactHandler;
@@ -83,15 +84,16 @@
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
+import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
+import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.resolver.filter.ExcludesArtifactFilter;
import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
import org.apache.maven.artifact.versioning.Restriction;
import org.apache.maven.artifact.versioning.VersionRange;
-import org.apache.maven.shared.dependency.graph.traversal.CollectingDependencyNodeVisitor;
import org.owasp.dependencycheck.agent.DependencyCheckScanAgent;
import org.owasp.dependencycheck.dependency.naming.GenericIdentifier;
@@ -99,6 +101,8 @@
import org.owasp.dependencycheck.dependency.naming.PurlIdentifier;
import org.apache.maven.shared.dependency.graph.traversal.DependencyNodeVisitor;
import org.apache.maven.shared.dependency.graph.traversal.FilteringDependencyNodeVisitor;
+import org.apache.maven.shared.transfer.dependencies.DefaultDependableCoordinate;
+import org.apache.maven.shared.transfer.dependencies.DependableCoordinate;
import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
import org.owasp.dependencycheck.reporting.ReportGenerator;
import org.owasp.dependencycheck.utils.SeverityUtil;
@@ -289,8 +293,9 @@ public abstract class BaseDependencyCheckMojo extends AbstractMojo implements Ma
@Parameter(property = "dependency-check.virtualSnapshotsFromReactor", defaultValue = "true")
private Boolean virtualSnapshotsFromReactor;
/**
- * The report format to be generated (HTML, XML, JUNIT, CSV, JSON, SARIF, JENKINS,
- * ALL). Multiple formats can be selected using a comma delineated list.
+ * The report format to be generated (HTML, XML, JUNIT, CSV, JSON, SARIF,
+ * JENKINS, ALL). Multiple formats can be selected using a comma delineated
+ * list.
*/
@SuppressWarnings("CanBeFinal")
@Parameter(property = "format", defaultValue = "HTML", required = true)
@@ -303,8 +308,9 @@ public abstract class BaseDependencyCheckMojo extends AbstractMojo implements Ma
@Parameter(property = "prettyPrint")
private Boolean prettyPrint;
/**
- * The report format to be generated (HTML, XML, JUNIT, CSV, JSON, SARIF, JENKINS,
- * ALL). Multiple formats can be selected using a comma delineated list.
+ * The report format to be generated (HTML, XML, JUNIT, CSV, JSON, SARIF,
+ * JENKINS, ALL). Multiple formats can be selected using a comma delineated
+ * list.
*/
@Parameter(property = "formats", required = true)
private String[] formats;
@@ -999,6 +1005,19 @@ public abstract class BaseDependencyCheckMojo extends AbstractMojo implements Ma
@Parameter(property = "scanDirectory")
private List scanDirectory;
+ /**
+ * Whether the project's plugins should also be scanned.
+ */
+ @SuppressWarnings("CanBeFinal")
+ @Parameter(property = "odc.plugins.scan", defaultValue = "false", required = false)
+ private boolean scanPlugins = false;
+ /**
+ * Whether the project's dependencies should also be scanned.
+ */
+ @SuppressWarnings("CanBeFinal")
+ @Parameter(property = "odc.dependencies.scan", defaultValue = "true", required = false)
+ private boolean scanDependencies = true;
+
//
//
/**
@@ -1172,12 +1191,13 @@ protected ExceptionCollection scanArtifacts(MavenProject project, Engine engine)
protected ExceptionCollection scanArtifacts(MavenProject project, Engine engine, boolean aggregate) {
try {
final List filterItems = Collections.singletonList(String.format("%s:%s", project.getGroupId(), project.getArtifactId()));
- final ProjectBuildingRequest buildingRequest = newResolveArtifactProjectBuildingRequest(project);
+ final ProjectBuildingRequest buildingRequest = newResolveArtifactProjectBuildingRequest(project, project.getRemoteArtifactRepositories());
//For some reason the filter does not filter out the project being analyzed
//if we pass in the filter below instead of null to the dependencyGraphBuilder
final DependencyNode dn = dependencyGraphBuilder.buildDependencyGraph(buildingRequest, null);
- final CollectingDependencyNodeVisitor collectorVisitor = new CollectingDependencyNodeVisitor();
+ final CollectingRootDependencyGraphVisitor collectorVisitor = new CollectingRootDependencyGraphVisitor();
+
// exclude artifact by pattern and its dependencies
final DependencyNodeVisitor transitiveFilterVisitor = new FilteringDependencyTransitiveNodeVisitor(collectorVisitor,
new ArtifactDependencyNodeFilter(new PatternExcludesArtifactFilter(getExcludes())));
@@ -1188,7 +1208,7 @@ protected ExceptionCollection scanArtifacts(MavenProject project, Engine engine,
dn.accept(artifactFilter);
//collect dependencies with the filter - see comment above.
- final List nodes = new ArrayList<>(collectorVisitor.getNodes());
+ final Map> nodes = collectorVisitor.getNodes();
return collectDependencies(engine, project, nodes, buildingRequest, aggregate);
} catch (DependencyGraphBuilderException ex) {
@@ -1198,6 +1218,146 @@ protected ExceptionCollection scanArtifacts(MavenProject project, Engine engine,
}
}
+ /**
+ * Scans the project's artifacts for plugin-dependencies and adds them to
+ * the engine's dependency list.
+ *
+ * @param project the project to scan the plugin-dependencies of
+ * @param engine the engine to use to scan the plugin-dependencies
+ * @param exCollection the collection of exceptions that have previously
+ * occurred
+ * @return a collection of exceptions that may have occurred while resolving
+ * and scanning the plugins and their dependencies
+ */
+ protected ExceptionCollection scanPlugins(MavenProject project, Engine engine, ExceptionCollection exCollection) {
+ ExceptionCollection exCol = exCollection;
+ final Set plugins = new HashSet<>();
+ final Set buildPlugins = getProject().getPluginArtifacts();
+ final Set reportPlugins = getProject().getReportArtifacts();
+ final Set extensions = getProject().getExtensionArtifacts();
+
+ plugins.addAll(buildPlugins);
+ plugins.addAll(reportPlugins);
+ plugins.addAll(extensions);
+
+ final ProjectBuildingRequest buildingRequest = newResolveArtifactProjectBuildingRequest(project, project.getPluginArtifactRepositories());
+ for (Artifact plugin : plugins) {
+ try {
+ final Artifact resolved = artifactResolver.resolveArtifact(buildingRequest, plugin).getArtifact();
+
+ exCol = addPluginToDependencies(project, engine, resolved, "pom.xml (plugins)", exCol);
+
+ final DefaultDependableCoordinate pluginCoordinate = new DefaultDependableCoordinate();
+ pluginCoordinate.setGroupId(resolved.getGroupId());
+ pluginCoordinate.setArtifactId(resolved.getArtifactId());
+ pluginCoordinate.setVersion(resolved.getVersion());
+
+ //TOOD - convert this to a packageURl instead of GAV
+ final String parent = resolved.getGroupId() + ":" + resolved.getArtifactId() + ":" + resolved.getVersion();
+ for (Artifact artifact : resolveArtifactDependencies(pluginCoordinate, project)) {
+ exCol = addPluginToDependencies(project, engine, artifact, parent, exCol);
+ }
+ } catch (ArtifactResolverException ex) {
+ throw new RuntimeException(ex);
+ } catch (IllegalArgumentException ex) {
+ throw new RuntimeException(ex);
+ } catch (DependencyResolverException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ return null;
+
+ }
+
+ private ExceptionCollection addPluginToDependencies(MavenProject project, Engine engine, Artifact artifact, String parent, ExceptionCollection exCollection) {
+ ExceptionCollection exCol = exCollection;
+ final String groupId = artifact.getGroupId();
+ final String artifactId = artifact.getArtifactId();
+ final String version = artifact.getVersion();
+ final File artifactFile = artifact.getFile();
+ if (artifactFile.isFile()) {
+ final List availableVersions = artifact.getAvailableVersions();
+
+ final List deps = engine.scan(artifactFile.getAbsoluteFile(),
+ project.getName() + " (plugins)");
+ if (deps != null) {
+ Dependency d = null;
+ if (deps.size() == 1) {
+ d = deps.get(0);
+ } else {
+ for (Dependency possible : deps) {
+ if (artifactFile.getAbsoluteFile().equals(possible.getActualFile())) {
+ d = possible;
+ break;
+ }
+ }
+ for (Dependency dep : deps) {
+ if (d != null && d != dep) {
+ final String includedBy = buildReference(groupId, artifactId, version);
+ dep.addIncludedBy(includedBy, "plugins");
+ }
+ }
+ }
+ if (d != null) {
+ final MavenArtifact ma = new MavenArtifact(groupId, artifactId, version);
+ d.addAsEvidence("pom", ma, Confidence.HIGHEST);
+ if (parent != null) {
+ d.addIncludedBy(parent, "plugins");
+ } else {
+ final String includedby = buildReference(
+ project.getGroupId(),
+ project.getArtifactId(),
+ project.getVersion());
+ d.addIncludedBy(includedby, "plugins");
+ }
+ if (availableVersions != null) {
+ for (ArtifactVersion av : availableVersions) {
+ d.addAvailableVersion(av.toString());
+ }
+ }
+ }
+ }
+ } else {
+ if (exCol == null) {
+ exCol = new ExceptionCollection();
+ }
+ exCol.addException(new DependencyNotFoundException("Unable to resolve plugin: "
+ + groupId + ":" + artifactId + ":" + version));
+ }
+
+ return exCol;
+ }
+
+ private String buildReference(final String groupId, final String artifactId, final String version) {
+ String includedBy;
+ try {
+ final PackageURL purl = new PackageURL("maven", groupId, artifactId, version, null, null);
+ includedBy = purl.toString();
+ } catch (MalformedPackageURLException ex) {
+ getLog().warn("Unable to generate build reference for " + groupId
+ + ":" + artifactId + ":" + version, ex);
+ includedBy = groupId + ":" + artifactId + ":" + version;
+ }
+ return includedBy;
+ }
+
+ protected Set resolveArtifactDependencies(final DependableCoordinate artifact, MavenProject project)
+ throws DependencyResolverException {
+ final ProjectBuildingRequest buildingRequest = newResolveArtifactProjectBuildingRequest(project, project.getRemoteArtifactRepositories());
+
+ final Iterable artifactResults = dependencyResolver.resolveDependencies(buildingRequest, artifact, null);
+
+ final Set artifacts = new HashSet<>();
+
+ for (ArtifactResult artifactResult : artifactResults) {
+ artifacts.add(artifactResult.getArtifact());
+ }
+
+ return artifacts;
+
+ }
+
/**
* Converts the dependency to a dependency node object.
*
@@ -1332,8 +1492,8 @@ private ExceptionCollection collectDependencyManagementDependencies(Engine engin
*
* @param engine the core dependency-check engine
* @param project the project being scanned
- * @param nodes the list of dependency nodes, generally obtained via the
- * DependencyGraphBuilder
+ * @param nodeMap the map of dependency nodes, generally obtained via the
+ * DependencyGraphBuilder using the CollectingRootDependencyGraphVisitor
* @param buildingRequest the Maven project building request
* @param aggregate whether the scan is part of an aggregate build
* @return a collection of exceptions that may have occurred while resolving
@@ -1341,180 +1501,22 @@ private ExceptionCollection collectDependencyManagementDependencies(Engine engin
*/
//CSOFF: OperatorWrap
private ExceptionCollection collectMavenDependencies(Engine engine, MavenProject project,
- List nodes, ProjectBuildingRequest buildingRequest, boolean aggregate) {
+ Map> nodeMap, ProjectBuildingRequest buildingRequest, boolean aggregate) {
- ExceptionCollection exCol = collectDependencyManagementDependencies(engine, buildingRequest, project, nodes, aggregate);
final List allResolvedDeps = new ArrayList<>();
- for (DependencyNode dependencyNode : nodes) {
- if (artifactScopeExcluded.passes(dependencyNode.getArtifact().getScope())
- || artifactTypeExcluded.passes(dependencyNode.getArtifact().getType())) {
- continue;
- }
-
- boolean isResolved = false;
- File artifactFile = null;
- String artifactId = null;
- String groupId = null;
- String version = null;
- List availableVersions = null;
- if (org.apache.maven.artifact.Artifact.SCOPE_SYSTEM.equals(dependencyNode.getArtifact().getScope())) {
- final Artifact a = dependencyNode.getArtifact();
- if (a.isResolved() && a.getFile().isFile()) {
- artifactFile = a.getFile();
- isResolved = artifactFile.isFile();
- groupId = a.getGroupId();
- artifactId = a.getArtifactId();
- version = a.getVersion();
- availableVersions = a.getAvailableVersions();
- } else {
- for (org.apache.maven.model.Dependency d : project.getDependencies()) {
- if (d.getSystemPath() != null && artifactsMatch(d, a)) {
- artifactFile = new File(d.getSystemPath());
- isResolved = artifactFile.isFile();
- groupId = a.getGroupId();
- artifactId = a.getArtifactId();
- version = a.getVersion();
- availableVersions = a.getAvailableVersions();
- break;
- }
- }
- }
- if (!isResolved) {
- getLog().error("Unable to resolve system scoped dependency: " + dependencyNode.toNodeString());
- if (exCol == null) {
- exCol = new ExceptionCollection();
- }
- exCol.addException(new DependencyNotFoundException("Unable to resolve system scoped dependency: "
- + dependencyNode.toNodeString()));
- }
- } else {
- final Artifact dependencyArtifact = dependencyNode.getArtifact();
- final Artifact result;
- if (dependencyArtifact.isResolved()) {
- //All transitive dependencies, excluding reactor and dependencyManagement artifacts should
- //have been resolved by Maven prior to invoking the plugin - resolving the dependencies
- //manually is unnecessary, and does not work in some cases (issue-1751)
- getLog().debug(String.format("Skipping artifact %s, already resolved", dependencyArtifact.getArtifactId()));
- result = dependencyArtifact;
- } else {
- try {
- if (allResolvedDeps.isEmpty()) { // no (partially successful) resolution attempt done
- try {
- final List dependencies = project.getDependencies();
- final List managedDependencies
- = project.getDependencyManagement() == null ? null : project.getDependencyManagement()
- .getDependencies();
- final Iterable allDeps
- = dependencyResolver.resolveDependencies(buildingRequest, dependencies, managedDependencies,
- null);
- allDeps.forEach(allResolvedDeps::add);
- } catch (DependencyResolverException dre) {
- if (dre.getCause() instanceof org.eclipse.aether.resolution.DependencyResolutionException) {
- final List successResults
- = Mshared998Util.getResolutionResults(
- (org.eclipse.aether.resolution.DependencyResolutionException) dre.getCause());
- allResolvedDeps.addAll(successResults);
- } else {
- throw dre;
- }
- }
- }
- result = findInAllDeps(allResolvedDeps, dependencyNode.getArtifact(), project);
- } catch (DependencyNotFoundException | DependencyResolverException ex) {
- getLog().debug(String.format("Aggregate : %s", aggregate));
- boolean addException = true;
- //CSOFF: EmptyBlock
- if (!aggregate) {
- // do nothing - the exception is to be reported
- } else if (addReactorDependency(engine, dependencyNode.getArtifact(), project)) {
- // successfully resolved as a reactor dependency - swallow the exception
- addException = false;
- }
- if (addException) {
- if (exCol == null) {
- exCol = new ExceptionCollection();
- }
- exCol.addException(ex);
- }
- continue;
- }
- }
- if (aggregate && virtualSnapshotsFromReactor
- && dependencyNode.getArtifact().isSnapshot()
- && addSnapshotReactorDependency(engine, dependencyNode.getArtifact(), project)) {
- continue;
- }
- isResolved = result.isResolved();
- artifactFile = result.getFile();
- groupId = result.getGroupId();
- artifactId = result.getArtifactId();
- version = result.getVersion();
- availableVersions = result.getAvailableVersions();
- }
- if (isResolved && artifactFile != null) {
- final List deps = engine.scan(artifactFile.getAbsoluteFile(),
- createProjectReferenceName(project, dependencyNode));
- if (deps != null) {
- scannedFiles.add(artifactFile);
- Dependency d = null;
- if (deps.size() == 1) {
- d = deps.get(0);
- } else {
- for (Dependency possible : deps) {
- if (artifactFile.getAbsoluteFile().equals(possible.getActualFile())) {
- d = possible;
- break;
- }
- }
- }
- if (d != null) {
- final MavenArtifact ma = new MavenArtifact(groupId, artifactId, version);
- d.addAsEvidence("pom", ma, Confidence.HIGHEST);
- if (availableVersions != null) {
- for (ArtifactVersion av : availableVersions) {
- d.addAvailableVersion(av.toString());
- }
- }
- getLog().debug(String.format("Adding project reference %s on dependency %s",
- project.getName(), d.getDisplayFileName()));
- } else if (getLog().isDebugEnabled()) {
- final String msg = String.format("More than 1 dependency was identified in first pass scan of '%s' in project %s",
- dependencyNode.getArtifact().getId(), project.getName());
- getLog().debug(msg);
- }
- } else if ("import".equals(dependencyNode.getArtifact().getScope())) {
- final String msg = String.format("Skipping '%s:%s' in project %s as it uses an `import` scope",
- dependencyNode.getArtifact().getId(), dependencyNode.getArtifact().getScope(), project.getName());
- getLog().debug(msg);
- } else if ("pom".equals(dependencyNode.getArtifact().getType())) {
+ //dependency management
+ final List dmNodes = new ArrayList<>();
+ ExceptionCollection exCol = collectDependencyManagementDependencies(engine, buildingRequest, project, dmNodes, aggregate);
+ for (DependencyNode dependencyNode : dmNodes) {
+ exCol = scanDependencyNode(dependencyNode, null, engine, project, allResolvedDeps, buildingRequest, aggregate, exCol);
+ }
- try {
- final Dependency d = new Dependency(artifactFile.getAbsoluteFile());
- final Model pom = PomUtils.readPom(artifactFile.getAbsoluteFile());
- JarAnalyzer.setPomEvidence(d, pom, null, true);
- engine.addDependency(d);
- } catch (AnalysisException ex) {
- if (exCol == null) {
- exCol = new ExceptionCollection();
- }
- exCol.addException(ex);
- getLog().debug("Error reading pom " + artifactFile.getAbsoluteFile(), ex);
- }
- } else {
- if (!scannedFiles.contains(artifactFile)) {
- final String msg = String.format("No analyzer could be found or the artifact has been scanned twice for '%s:%s' in project %s",
- dependencyNode.getArtifact().getId(), dependencyNode.getArtifact().getScope(), project.getName());
- getLog().warn(msg);
- }
- }
- } else {
- final String msg = String.format("Unable to resolve '%s' in project %s",
- dependencyNode.getArtifact().getId(), project.getName());
- getLog().debug(msg);
- if (exCol == null) {
- exCol = new ExceptionCollection();
- }
+ //dependencies
+ for (Map.Entry> entry : nodeMap.entrySet()) {
+ exCol = scanDependencyNode(entry.getKey(), null, engine, project, allResolvedDeps, buildingRequest, aggregate, exCol);
+ for (DependencyNode dependencyNode : entry.getValue()) {
+ exCol = scanDependencyNode(dependencyNode, entry.getKey(), engine, project, allResolvedDeps, buildingRequest, aggregate, exCol);
}
}
return exCol;
@@ -1602,7 +1604,7 @@ protected String createProjectReferenceName(MavenProject project, DependencyNode
* and scanning the dependencies
*/
private ExceptionCollection collectDependencies(Engine engine, MavenProject project,
- List nodes, ProjectBuildingRequest buildingRequest, boolean aggregate) {
+ Map> nodes, ProjectBuildingRequest buildingRequest, boolean aggregate) {
ExceptionCollection exCol;
exCol = collectMavenDependencies(engine, project, nodes, buildingRequest, aggregate);
@@ -1764,7 +1766,11 @@ private boolean addVirtualDependencyFromReactor(Engine engine, Artifact artifact
d.setEcosystem(JarAnalyzer.DEPENDENCY_ECOSYSTEM);
d.setDisplayFileName(displayName);
d.addProjectReference(depender.getName());
-
+ final String includedby = buildReference(
+ depender.getGroupId(),
+ depender.getArtifactId(),
+ depender.getVersion());
+ d.addIncludedBy(includedby);
d.addEvidence(EvidenceType.PRODUCT, "project", "artifactid", prj.getArtifactId(), Confidence.HIGHEST);
d.addEvidence(EvidenceType.VENDOR, "project", "artifactid", prj.getArtifactId(), Confidence.LOW);
@@ -1843,13 +1849,14 @@ private boolean addSnapshotReactorDependency(Engine engine, Artifact artifact, f
/**
* @param project The target project to create a building request for.
+ * @param repos the artifact repositories to use.
* @return Returns a new ProjectBuildingRequest populated from the current
* session and the target project remote repositories, used to resolve
* artifacts.
*/
- public ProjectBuildingRequest newResolveArtifactProjectBuildingRequest(MavenProject project) {
+ public ProjectBuildingRequest newResolveArtifactProjectBuildingRequest(MavenProject project, List repos) {
final ProjectBuildingRequest buildingRequest = new DefaultProjectBuildingRequest(session.getProjectBuildingRequest());
- buildingRequest.setRemoteRepositories(new ArrayList<>(project.getRemoteArtifactRepositories()));
+ buildingRequest.setRemoteRepositories(repos);
buildingRequest.setProject(project);
return buildingRequest;
}
@@ -1865,7 +1872,13 @@ public ProjectBuildingRequest newResolveArtifactProjectBuildingRequest(MavenProj
protected void runCheck() throws MojoExecutionException, MojoFailureException {
muteJCS();
try (Engine engine = initializeEngine()) {
- ExceptionCollection exCol = scanDependencies(engine);
+ ExceptionCollection exCol = null;
+ if (scanDependencies) {
+ exCol = scanDependencies(engine);
+ }
+ if (scanPlugins) {
+ exCol = scanPlugins(engine, exCol);
+ }
try {
engine.analyzeDependencies();
} catch (ExceptionCollection ex) {
@@ -1955,7 +1968,7 @@ private ExceptionCollection handleAnalysisExceptions(ExceptionCollection current
}
/**
- * Scans the dependencies of the projects in aggregate.
+ * Scans the dependencies of the projects.
*
* @param engine the engine used to perform the scanning
* @return a collection of exceptions
@@ -1963,6 +1976,17 @@ private ExceptionCollection handleAnalysisExceptions(ExceptionCollection current
*/
protected abstract ExceptionCollection scanDependencies(Engine engine) throws MojoExecutionException;
+ /**
+ * Scans the plugins of the projects.
+ *
+ * @param engine the engine used to perform the scanning
+ * @param exCol the collection of any exceptions that have previously been
+ * captured.
+ * @return a collection of exceptions
+ * @throws MojoExecutionException thrown if a fatal exception occurs
+ */
+ protected abstract ExceptionCollection scanPlugins(Engine engine, ExceptionCollection exCol) throws MojoExecutionException;
+
/**
* Returns the report output directory.
*
@@ -2186,7 +2210,7 @@ protected void populateSettings() {
settings.setBooleanIfNotNull(Settings.KEYS.ANALYZER_NODE_AUDIT_SKIPDEV, nodeAuditSkipDevDependencies);
settings.setBooleanIfNotNull(Settings.KEYS.ANALYZER_YARN_AUDIT_ENABLED, yarnAuditAnalyzerEnabled);
settings.setBooleanIfNotNull(Settings.KEYS.ANALYZER_PNPM_AUDIT_ENABLED, pnpmAuditAnalyzerEnabled);
-
+
settings.setBooleanIfNotNull(Settings.KEYS.ANALYZER_RETIREJS_ENABLED, retireJsAnalyzerEnabled);
settings.setStringIfNotNull(Settings.KEYS.ANALYZER_RETIREJS_REPO_JS_URL, retireJsUrl);
settings.setBooleanIfNotNull(Settings.KEYS.ANALYZER_RETIREJS_FORCEUPDATE, retireJsForceUpdate);
@@ -2582,5 +2606,219 @@ private String getDefaultCveUrlModified() {
}
//
+ //CSOFF: ParameterNumber
+ private ExceptionCollection scanDependencyNode(DependencyNode dependencyNode, DependencyNode root,
+ Engine engine, MavenProject project, List allResolvedDeps,
+ ProjectBuildingRequest buildingRequest, boolean aggregate, ExceptionCollection exceptionCollection) {
+ ExceptionCollection exCol = exceptionCollection;
+ if (artifactScopeExcluded.passes(dependencyNode.getArtifact().getScope())
+ || artifactTypeExcluded.passes(dependencyNode.getArtifact().getType())) {
+ return exCol;
+ }
+
+ boolean isResolved = false;
+ File artifactFile = null;
+ String artifactId = null;
+ String groupId = null;
+ String version = null;
+ List availableVersions = null;
+ if (org.apache.maven.artifact.Artifact.SCOPE_SYSTEM.equals(dependencyNode.getArtifact().getScope())) {
+ final Artifact a = dependencyNode.getArtifact();
+ if (a.isResolved() && a.getFile().isFile()) {
+ artifactFile = a.getFile();
+ isResolved = artifactFile.isFile();
+ groupId = a.getGroupId();
+ artifactId = a.getArtifactId();
+ version = a.getVersion();
+ availableVersions = a.getAvailableVersions();
+ } else {
+ for (org.apache.maven.model.Dependency d : project.getDependencies()) {
+ if (d.getSystemPath() != null && artifactsMatch(d, a)) {
+ artifactFile = new File(d.getSystemPath());
+ isResolved = artifactFile.isFile();
+ groupId = a.getGroupId();
+ artifactId = a.getArtifactId();
+ version = a.getVersion();
+ availableVersions = a.getAvailableVersions();
+ break;
+ }
+ }
+ }
+ if (!isResolved) {
+ getLog().error("Unable to resolve system scoped dependency: " + dependencyNode.toNodeString());
+ if (exCol == null) {
+ exCol = new ExceptionCollection();
+ }
+ exCol.addException(new DependencyNotFoundException("Unable to resolve system scoped dependency: "
+ + dependencyNode.toNodeString()));
+ }
+ } else {
+ final Artifact dependencyArtifact = dependencyNode.getArtifact();
+ final Artifact result;
+ if (dependencyArtifact.isResolved()) {
+ //All transitive dependencies, excluding reactor and dependencyManagement artifacts should
+ //have been resolved by Maven prior to invoking the plugin - resolving the dependencies
+ //manually is unnecessary, and does not work in some cases (issue-1751)
+ getLog().debug(String.format("Skipping artifact %s, already resolved", dependencyArtifact.getArtifactId()));
+ result = dependencyArtifact;
+ } else {
+ try {
+ if (allResolvedDeps.isEmpty()) { // no (partially successful) resolution attempt done
+ try {
+ final List dependencies = project.getDependencies();
+ final List managedDependencies = project
+ .getDependencyManagement() == null ? null : project.getDependencyManagement().getDependencies();
+ final Iterable allDeps = dependencyResolver
+ .resolveDependencies(buildingRequest, dependencies, managedDependencies, null);
+ allDeps.forEach(allResolvedDeps::add);
+ } catch (DependencyResolverException dre) {
+ if (dre.getCause() instanceof org.eclipse.aether.resolution.DependencyResolutionException) {
+ final List successResults = Mshared998Util
+ .getResolutionResults((org.eclipse.aether.resolution.DependencyResolutionException) dre.getCause());
+ allResolvedDeps.addAll(successResults);
+ } else {
+ throw dre;
+ }
+ }
+ }
+ result = findInAllDeps(allResolvedDeps, dependencyNode.getArtifact(), project);
+ } catch (DependencyNotFoundException | DependencyResolverException ex) {
+ getLog().debug(String.format("Aggregate : %s", aggregate));
+ boolean addException = true;
+ //CSOFF: EmptyBlock
+ if (!aggregate) {
+ // do nothing - the exception is to be reported
+ } else if (addReactorDependency(engine, dependencyNode.getArtifact(), project)) {
+ // successfully resolved as a reactor dependency - swallow the exception
+ addException = false;
+ }
+ if (addException) {
+ if (exCol == null) {
+ exCol = new ExceptionCollection();
+ }
+ exCol.addException(ex);
+ }
+ return exCol;
+ }
+ }
+ if (aggregate && virtualSnapshotsFromReactor
+ && dependencyNode.getArtifact().isSnapshot()
+ && addSnapshotReactorDependency(engine, dependencyNode.getArtifact(), project)) {
+ return exCol;
+ }
+ isResolved = result.isResolved();
+ artifactFile = result.getFile();
+ groupId = result.getGroupId();
+ artifactId = result.getArtifactId();
+ version = result.getVersion();
+ availableVersions = result.getAvailableVersions();
+ }
+ if (isResolved && artifactFile != null) {
+ final List deps = engine.scan(artifactFile.getAbsoluteFile(),
+ createProjectReferenceName(project, dependencyNode));
+ if (deps != null) {
+ processResolvedArtifact(artifactFile, deps, groupId, artifactId, version, root, project, availableVersions, dependencyNode);
+ } else if ("import".equals(dependencyNode.getArtifact().getScope())) {
+ final String msg = String.format("Skipping '%s:%s' in project %s as it uses an `import` scope",
+ dependencyNode.getArtifact().getId(), dependencyNode.getArtifact().getScope(), project.getName());
+ getLog().debug(msg);
+ } else if ("pom".equals(dependencyNode.getArtifact().getType())) {
+ exCol = processPomArtifact(artifactFile, root, project, engine, exCol);
+ } else {
+ if (!scannedFiles.contains(artifactFile)) {
+ final String msg = String.format("No analyzer could be found or the artifact has been scanned twice for '%s:%s' in project %s",
+ dependencyNode.getArtifact().getId(), dependencyNode.getArtifact().getScope(), project.getName());
+ getLog().warn(msg);
+ }
+ }
+ } else {
+ final String msg = String.format("Unable to resolve '%s' in project %s",
+ dependencyNode.getArtifact().getId(), project.getName());
+ getLog().debug(msg);
+ if (exCol == null) {
+ exCol = new ExceptionCollection();
+ }
+ }
+ return exCol;
+ }
+ //CSON: ParameterNumber
+
+ //CSOFF: ParameterNumber
+ private void processResolvedArtifact(File artifactFile, final List deps,
+ String groupId, String artifactId, String version, DependencyNode root,
+ MavenProject project1, List availableVersions,
+ DependencyNode dependencyNode) {
+ scannedFiles.add(artifactFile);
+ Dependency d = null;
+ if (deps.size() == 1) {
+ d = deps.get(0);
+
+ } else {
+ for (Dependency possible : deps) {
+ if (artifactFile.getAbsoluteFile().equals(possible.getActualFile())) {
+ d = possible;
+ break;
+ }
+ }
+ for (Dependency dep : deps) {
+ if (d != null && d != dep) {
+ final String includedBy = buildReference(groupId, artifactId, version);
+ dep.addIncludedBy(includedBy);
+ }
+ }
+ }
+ if (d != null) {
+ final MavenArtifact ma = new MavenArtifact(groupId, artifactId, version);
+ d.addAsEvidence("pom", ma, Confidence.HIGHEST);
+ if (root != null) {
+ final String includedby = buildReference(
+ root.getArtifact().getGroupId(),
+ root.getArtifact().getArtifactId(),
+ root.getArtifact().getVersion());
+ d.addIncludedBy(includedby);
+ } else {
+ final String includedby = buildReference(project1.getGroupId(), project1.getArtifactId(), project1.getVersion());
+ d.addIncludedBy(includedby);
+ }
+ if (availableVersions != null) {
+ for (ArtifactVersion av : availableVersions) {
+ d.addAvailableVersion(av.toString());
+ }
+ }
+ getLog().debug(String.format("Adding project reference %s on dependency %s", project1.getName(), d.getDisplayFileName()));
+ } else if (getLog().isDebugEnabled()) {
+ final String msg = String.format("More than 1 dependency was identified in first pass scan of '%s' in project %s", dependencyNode.getArtifact().getId(), project1.getName());
+ getLog().debug(msg);
+ }
+ }
+ //CSON: ParameterNumber
+
+ private ExceptionCollection processPomArtifact(File artifactFile, DependencyNode root,
+ MavenProject project1, Engine engine, ExceptionCollection exCollection) {
+ ExceptionCollection exCol = exCollection;
+ try {
+ final Dependency d = new Dependency(artifactFile.getAbsoluteFile());
+ final Model pom = PomUtils.readPom(artifactFile.getAbsoluteFile());
+ JarAnalyzer.setPomEvidence(d, pom, null, true);
+ if (root != null) {
+ final String includedby = buildReference(
+ root.getArtifact().getGroupId(),
+ root.getArtifact().getArtifactId(),
+ root.getArtifact().getVersion());
+ d.addIncludedBy(includedby);
+ } else {
+ final String includedby = buildReference(project1.getGroupId(), project1.getArtifactId(), project1.getVersion());
+ d.addIncludedBy(includedby);
+ }
+ engine.addDependency(d);
+ } catch (AnalysisException ex) {
+ if (exCol == null) {
+ exCol = new ExceptionCollection();
+ }
+ exCol.addException(ex);
+ getLog().debug("Error reading pom " + artifactFile.getAbsoluteFile(), ex);
+ }
+ return exCol;
+ }
}
//CSON: FileLength
diff --git a/maven/src/main/java/org/owasp/dependencycheck/maven/CheckMojo.java b/maven/src/main/java/org/owasp/dependencycheck/maven/CheckMojo.java
index a7da4da4b12..85c8777a37b 100644
--- a/maven/src/main/java/org/owasp/dependencycheck/maven/CheckMojo.java
+++ b/maven/src/main/java/org/owasp/dependencycheck/maven/CheckMojo.java
@@ -104,4 +104,19 @@ protected ExceptionCollection scanDependencies(final Engine engine) throws MojoE
return scanArtifacts(getProject(), engine);
}
+ /**
+ * Scans the plugins of the project.
+ *
+ * @param engine the engine used to perform the scanning
+ * @param exCollection the collection of exceptions that might have occurred
+ * previously
+ * @return a collection of exceptions
+ * @throws MojoExecutionException thrown if a fatal exception occurs
+ */
+ @Override
+ protected ExceptionCollection scanPlugins(final Engine engine, final ExceptionCollection exCollection) throws MojoExecutionException {
+ final ExceptionCollection exCol = scanPlugins(getProject(), engine, exCollection);
+ return exCol;
+ }
+
}
diff --git a/maven/src/main/java/org/owasp/dependencycheck/maven/CollectingRootDependencyGraphVisitor.java b/maven/src/main/java/org/owasp/dependencycheck/maven/CollectingRootDependencyGraphVisitor.java
new file mode 100644
index 00000000000..97f493885b2
--- /dev/null
+++ b/maven/src/main/java/org/owasp/dependencycheck/maven/CollectingRootDependencyGraphVisitor.java
@@ -0,0 +1,72 @@
+/*
+ * This file is part of dependency-check-maven.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * Copyright (c) 2022 Jeremy Long. All Rights Reserved.
+ */
+package org.owasp.dependencycheck.maven;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.maven.shared.dependency.graph.DependencyNode;
+import org.apache.maven.shared.dependency.graph.traversal.DependencyNodeVisitor;
+
+/**
+ *
+ * @author Jeremy Long
+ */
+public class CollectingRootDependencyGraphVisitor implements DependencyNodeVisitor {
+
+ /**
+ * The map of nodes collected by root nodes.
+ */
+ private final Map> nodes = new HashMap<>();
+ /**
+ * A reference to the root node of the dependency tree.
+ */
+ private DependencyNode root;
+ /**
+ * Track the depth of the dependency tree.
+ */
+ private int depth = 0;
+
+ @Override
+ public boolean visit(DependencyNode node) {
+ if (depth == 0) {
+ root = node;
+ if (!nodes.containsKey(root)) {
+ nodes.put(root, new ArrayList<>());
+ }
+ } else {
+ // collect node
+ nodes.get(root).add(node);
+ }
+ depth += 1;
+ return true;
+ }
+
+ @Override
+ public boolean endVisit(DependencyNode node) {
+ depth -= 1;
+ return true;
+ }
+
+ public Map> getNodes() {
+ return Collections.unmodifiableMap(nodes);
+ }
+
+}
diff --git a/maven/src/main/java/org/owasp/dependencycheck/maven/PurgeMojo.java b/maven/src/main/java/org/owasp/dependencycheck/maven/PurgeMojo.java
index ec42d6c372c..5444262207d 100644
--- a/maven/src/main/java/org/owasp/dependencycheck/maven/PurgeMojo.java
+++ b/maven/src/main/java/org/owasp/dependencycheck/maven/PurgeMojo.java
@@ -104,4 +104,18 @@ public String getDescription(Locale locale) {
protected ExceptionCollection scanDependencies(Engine engine) throws MojoExecutionException {
throw new UnsupportedOperationException("Operation not supported");
}
+
+ /**
+ * Throws an exception if called. The purge mojo does not scan dependencies.
+ *
+ * @param engine the engine used to scan
+ * @param exCollection the collection of exceptions that might have occurred
+ * previously
+ * @return a collection of exceptions
+ * @throws MojoExecutionException thrown if there is an exception
+ */
+ @Override
+ protected ExceptionCollection scanPlugins(final Engine engine, final ExceptionCollection exCollection) throws MojoExecutionException {
+ throw new UnsupportedOperationException("Operation not supported");
+ }
}
diff --git a/maven/src/main/java/org/owasp/dependencycheck/maven/UpdateMojo.java b/maven/src/main/java/org/owasp/dependencycheck/maven/UpdateMojo.java
index b0d444ba644..0f2159bd5dc 100644
--- a/maven/src/main/java/org/owasp/dependencycheck/maven/UpdateMojo.java
+++ b/maven/src/main/java/org/owasp/dependencycheck/maven/UpdateMojo.java
@@ -131,4 +131,18 @@ public String getDescription(Locale locale) {
protected ExceptionCollection scanDependencies(Engine engine) throws MojoExecutionException {
throw new UnsupportedOperationException("Operation not supported");
}
+
+ /**
+ * Throws an exception if called. The purge mojo does not scan dependencies.
+ *
+ * @param engine the engine used to scan
+ * @param exCollection the collection of exceptions that might have occurred
+ * previously
+ * @return a collection of exceptions
+ * @throws MojoExecutionException thrown if there is an exception
+ */
+ @Override
+ protected ExceptionCollection scanPlugins(final Engine engine, final ExceptionCollection exCollection) throws MojoExecutionException {
+ throw new UnsupportedOperationException("Operation not supported");
+ }
}
diff --git a/maven/src/site/markdown/configuration.md b/maven/src/site/markdown/configuration.md
index cfe5f424ad8..c73b1628e15 100644
--- a/maven/src/site/markdown/configuration.md
+++ b/maven/src/site/markdown/configuration.md
@@ -27,6 +27,8 @@ name | The name of the report in the site. | dependency-c
outputDirectory | The location to write the report(s). This can be specified on the command line via `-Dodc.outputDirectory`. Note, this is not used if generating the report as part of a `mvn site` build. | 'target'
scanSet | An optional collection of file sets that specify additional files and/or directories to analyze as part of the scan. If not specified, defaults to standard Maven conventions. This cannot be configured via the command line parameters (e.g. `-DscanSet=./path`) - use the below `scanDirectory` instead. Note that the scan sets specified should be relative from the base directory - do not use Maven project variable substitution (e.g. `${project.basedir}/src/webpack`). Using Maven project variable substitution can cause directories to be missed especially when using an aggregate build. | ['src/main/resources', 'src/main/filters', 'src/main/webapp', './package.json', './package-lock.json', './npm-shrinkwrap.json', './Gopkg.lock', './go.mod']
scanDirectory | An optional collection of directories to include in the scan. This configuration should only be used via the command line - if configuring the scan directories within the `pom.xml` please consider using the above `scanSet`. |
+scanDependencies | Sets whether the dependencies should be scanned. | true
+scanPlugins | Sets whether the plugins and their dependencies should be scanned. | false
skip | Skips the dependency-check analysis. | false
skipProvidedScope | Skip analysis for artifacts with Provided Scope. | false
skipRuntimeScope | Skip analysis for artifacts with Runtime Scope. | false
diff --git a/maven/src/test/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojoTest.java b/maven/src/test/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojoTest.java
index 89d7193eee8..ca13bcb0126 100644
--- a/maven/src/test/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojoTest.java
+++ b/maven/src/test/java/org/owasp/dependencycheck/maven/BaseDependencyCheckMojoTest.java
@@ -231,6 +231,10 @@ public boolean canGenerateReport() {
protected ExceptionCollection scanDependencies(Engine engine) throws MojoExecutionException {
throw new UnsupportedOperationException("Operation not supported");
}
+ @Override
+ protected ExceptionCollection scanPlugins(Engine engine, ExceptionCollection exCollection) throws MojoExecutionException {
+ throw new UnsupportedOperationException("Operation not supported");
+ }
}
@Test
diff --git a/src/site/markdown/dependency-check-gradle/configuration-aggregate.md b/src/site/markdown/dependency-check-gradle/configuration-aggregate.md
index 2bb48d09378..c62b0aa53c9 100644
--- a/src/site/markdown/dependency-check-gradle/configuration-aggregate.md
+++ b/src/site/markdown/dependency-check-gradle/configuration-aggregate.md
@@ -25,27 +25,29 @@ apply plugin: 'org.owasp.dependencycheck'
check.dependsOn dependencyCheckAggregate
```
-Property | Description | Default Value
----------------------|--------------------------------------------------------------------------------------------------------------------|------------------
-autoUpdate | Sets whether auto-updating of the NVD CVE/CPE data is enabled. It is not recommended that this be turned to false. | true
-analyzedTypes | The default artifact types that will be analyzed. | ['jar', 'aar', 'js', 'war', 'ear', 'zip']
-cveValidForHours | Sets the number of hours to wait before checking for new updates from the NVD. | 4
-format | The report format to be generated (HTML, XML, CSV, JSON, JUNIT, SARIF, ALL). | HTML
-formats | A list of report formats to be generated (HTML, XML, CSV, JSON, JUNIT, SARIF, ALL). |
-junitFailOnCVSS | If using the JUNIT report format the junitFailOnCVSS sets the CVSS score threshold that is considered a failure. | 0
+Property | Description | Default Value
+---------------------|----------------------------------------------------------------------------------------------------------------------|------------------
+autoUpdate | Sets whether auto-updating of the NVD CVE/CPE data is enabled. It is not recommended that this be turned to false. | true
+analyzedTypes | The default artifact types that will be analyzed. | ['jar', 'aar', 'js', 'war', 'ear', 'zip']
+cveValidForHours | Sets the number of hours to wait before checking for new updates from the NVD. | 4
+format | The report format to be generated (HTML, XML, CSV, JSON, JUNIT, SARIF, ALL). | HTML
+formats | A list of report formats to be generated (HTML, XML, CSV, JSON, JUNIT, SARIF, ALL). |
+junitFailOnCVSS | If using the JUNIT report format the junitFailOnCVSS sets the CVSS score threshold that is considered a failure. | 0
failBuildOnCVSS | Specifies if the build should be failed if a CVSS score equal to or above a specified level is identified. The default is 11; since the CVSS scores are 0-10, by default the build will never fail. More information on CVSS scores can be found at the [NVD](https://nvd.nist.gov/vuln-metrics/cvss)| 11
-failOnError | Fails the build if an error occurs during the dependency-check analysis. | true
-outputDirectory | The location to write the report(s). This directory will be located in the build directory. | ${buildDir}/reports
-skipTestGroups | When set to true (the default) all dependency groups that being with 'test' will be skipped. | true
+failOnError | Fails the build if an error occurs during the dependency-check analysis. | true
+outputDirectory | The location to write the report(s). This directory will be located in the build directory. | ${buildDir}/reports
+skipTestGroups | When set to true (the default) all dependency groups that being with 'test' will be skipped. | true
suppressionFile | The file path to the XML suppression file \- used to suppress [false positives](../general/suppression.html). The configured value can be a local file path, a URL to a suppression file, or even a reference to a file on the class path (see https://github.com/jeremylong/DependencyCheck/issues/1878#issuecomment-487533799) |
suppressionFiles | A list of file paths to the XML suppression files \- used to suppress [false positives](../general/suppression.html). The configured values can be a local file path, a URL to a suppression file, or even a reference to a file on the class path (see https://github.com/jeremylong/DependencyCheck/issues/1878#issuecomment-487533799) |
-hintsFile | The file path to the XML hints file \- used to resolve [false negatives](../general/hints.html) |
-skip | If set to true dependency-check analysis will be skipped. | false
-skipConfigurations | A list of configurations that will be skipped. This is mutually exclusive with the scanConfigurations property. | `[]` which means no configuration is skipped.
+hintsFile | The file path to the XML hints file \- used to resolve [false negatives](../general/hints.html) |
+skip | If set to true dependency-check analysis will be skipped. | false
+skipConfigurations | A list of configurations that will be skipped. This is mutually exclusive with the scanConfigurations property. | `[]` which means no configuration is skipped.
scanConfigurations | A list of configurations that will be scanned, all other configurations are skipped. This is mutually exclusive with the skipConfigurations property. | `[]` which implicitly means all configurations are scanned.
scanProjects | A list of projects that will be scanned, all other projects are skipped. The list or projects to skip must include a preceding colon: `scanProjects = [':app']`. This is mutually exclusive with the `skipProjects` property. | `[]` which implicitly means all projects get scanned.
skipProjects | A list of projects that will be skipped. The list or projects to skip must include a preceding colon: `skipProjects = [':sub1']`. This is mutually exclusive with the `scanProjects` property. | `[]` which means no projects are skipped.
-scanSet | A list of directories that will be scanned for additional dependencies. | ['src/main/resources','src/main/webapp', './package.json', './package-lock.json', './npm-shrinkwrap.json', './Gopkg.lock', './go.mod']
+scanBuildEnv | A boolean indicating whether to scan the `buildEnv`. | false
+scanDependencies | A boolean indicating whether to scan the `dependencies`. | true
+scanSet | A list of directories that will be scanned for additional dependencies. | ['src/main/resources','src/main/webapp', './package.json', './package-lock.json', './npm-shrinkwrap.json', './Gopkg.lock', './go.mod']
#### Example
```groovy
diff --git a/src/site/markdown/dependency-check-gradle/configuration.md b/src/site/markdown/dependency-check-gradle/configuration.md
index 94c4677a8fe..9502be2b62e 100644
--- a/src/site/markdown/dependency-check-gradle/configuration.md
+++ b/src/site/markdown/dependency-check-gradle/configuration.md
@@ -45,6 +45,8 @@ skipConfigurations | A list of configurations that will be skipped. This is mu
scanConfigurations | A list of configurations that will be scanned, all other configurations are skipped. This is mutually exclusive with the skipConfigurations property. | `[]` which implicitly means all configurations get scanned.
scanProjects | A list of projects that will be scanned, all other projects are skipped. The list or projects to skip must include a preceding colon: `scanProjects = [':app']`. This is mutually exclusive with the `skipProjects` property. | `[]` which implicitly means all projects get scanned.
skipProjects | A list of projects that will be skipped. The list or projects to skip must include a preceding colon: `skipProjects = [':sub1']`. This is mutually exclusive with the `scanProjects` property. | `[]` which means no projects are skipped.
+scanBuildEnv | A boolean indicating whether to scan the `buildEnv`. | false
+scanDependencies | A boolean indicating whether to scan the `dependencies`. | true
scanSet | A list of directories that will be scanned for additional dependencies. | ['src/main/resources','src/main/webapp']
#### Example