diff --git a/README.md b/README.md
index c924b00c2..d9db978e7 100644
--- a/README.md
+++ b/README.md
@@ -136,6 +136,69 @@ docker run --rm \
```
+Upgrade Notes
+-------------
+
+### Upgrading from **1.x.x** to **2.x.x**
+
+Note that when upgrading from version 1.x.x that the following changes will need to be made to your configuration.
+
+#### Suppression file
+
+In order to support multiple suppression files, the mechanism for configuring suppression files has changed.
+As such, users that have defined a suppression file in their configuration will need to update.
+
+See the examples below:
+
+##### Ant
+
+Old:
+
+```xml
+
+
+```
+
+New:
+
+```xml
+
+ suppression.xml
+
+```
+
+##### Maven
+
+Old:
+
+```xml
+
+ org.owasp
+ dependency-check-maven
+
+ suppression.xml
+
+
+```
+
+New:
+
+```xml
+
+ org.owasp
+ dependency-check-maven
+
+
+ suppression.xml
+
+
+
+```
+
+
Mailing List
------------
diff --git a/dependency-check-ant/src/main/java/org/owasp/dependencycheck/taskdefs/Check.java b/dependency-check-ant/src/main/java/org/owasp/dependencycheck/taskdefs/Check.java
index 65b6cd9a8..5af32b863 100644
--- a/dependency-check-ant/src/main/java/org/owasp/dependencycheck/taskdefs/Check.java
+++ b/dependency-check-ant/src/main/java/org/owasp/dependencycheck/taskdefs/Check.java
@@ -461,7 +461,7 @@ public class Check extends Update {
*/
@Deprecated
public void setSuppressionFile(String suppressionFile) {
- throw new BuildException("Property form of suppressionFile has been replaced by a nested element, please update your configuration.");
+ throw new BuildException("Definition of a suppression file via a property has been deprecated. Suppression files are now defined as a nested element, please update your configuration.");
}
/**
diff --git a/dependency-check-ant/src/test/java/org/owasp/dependencycheck/taskdefs/DependencyCheckTaskTest.java b/dependency-check-ant/src/test/java/org/owasp/dependencycheck/taskdefs/DependencyCheckTaskTest.java
index 87beaf6d9..dfb4c86e5 100644
--- a/dependency-check-ant/src/test/java/org/owasp/dependencycheck/taskdefs/DependencyCheckTaskTest.java
+++ b/dependency-check-ant/src/test/java/org/owasp/dependencycheck/taskdefs/DependencyCheckTaskTest.java
@@ -140,7 +140,7 @@ public class DependencyCheckTaskTest {
// WHEN executing the ant task
// THEN an exception with a warning is thrown
expectedException.expect(BuildException.class);
- expectedException.expectMessage("Property form of suppressionFile has been replaced by a nested element, please update your configuration.");
+ expectedException.expectMessage("Definition of a suppression file via a property has been deprecated. Suppression files are now defined as a nested element, please update your configuration.");
buildFileRule.executeTarget(antTaskName);
}
}
diff --git a/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/AbstractSuppressionAnalyzerTest.java b/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/AbstractSuppressionAnalyzerTest.java
index 119d90bd0..704cf32dd 100644
--- a/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/AbstractSuppressionAnalyzerTest.java
+++ b/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/AbstractSuppressionAnalyzerTest.java
@@ -17,30 +17,34 @@
*/
package org.owasp.dependencycheck.analyzer;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+
+import java.util.Set;
+
import org.junit.Before;
import org.junit.Test;
import org.owasp.dependencycheck.BaseTest;
import org.owasp.dependencycheck.Engine;
import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
import org.owasp.dependencycheck.dependency.Dependency;
-import org.owasp.dependencycheck.xml.suppression.SuppressionRule;
-import org.owasp.dependencycheck.utils.Settings;
-import org.slf4j.LoggerFactory;
-
-import java.net.MalformedURLException;
-import java.net.URISyntaxException;
-import java.util.List;
-import java.util.Set;
-
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
import org.owasp.dependencycheck.exception.InitializationException;
+import org.owasp.dependencycheck.utils.Settings;
+import org.owasp.dependencycheck.utils.Settings.KEYS;
/**
* @author Jeremy Long
*/
public class AbstractSuppressionAnalyzerTest extends BaseTest {
+ /** A second suppression file to test with. */
+ private static final String OTHER_SUPPRESSIONS_FILE = "other-suppressions.xml";
+
+ /** Suppression file to test with. */
+ private static final String SUPPRESSIONS_FILE = "suppressions.xml";
+
private AbstractSuppressionAnalyzer instance;
@Before
@@ -64,24 +68,42 @@ public class AbstractSuppressionAnalyzerTest extends BaseTest {
*/
@Test
public void testGetRulesFromSuppressionFileFromURL() throws Exception {
- setSupressionFileFromURL();
- instance.initialize();
- int expCount = 5;
- List result = instance.getRules();
- assertTrue(expCount <= result.size());
+ final String fileUrl = getClass().getClassLoader().getResource(SUPPRESSIONS_FILE).toURI().toURL().toString();
+ final int numberOfExtraLoadedRules = getNumberOfRulesLoadedFromPath(fileUrl) - getNumberOfRulesLoadedInCoreFile();
+ assertEquals("Expected 5 extra rules in the given path", 5, numberOfExtraLoadedRules);
}
/**
* Test of getRules method, of class AbstractSuppressionAnalyzer for
- * suppression file declared as URL.
+ * suppression file on the classpath.
*/
@Test
public void testGetRulesFromSuppressionFileInClasspath() throws Exception {
- Settings.setString(Settings.KEYS.SUPPRESSION_FILE, "suppressions.xml");
+ final int numberOfExtraLoadedRules = getNumberOfRulesLoadedFromPath(SUPPRESSIONS_FILE) - getNumberOfRulesLoadedInCoreFile();
+ assertEquals("Expected 5 extra rules in the given file", 5, numberOfExtraLoadedRules);
+ }
+
+ /**
+ * Assert that rules are loaded from multiple files if multiple files are denfined in the {@link Settings} singleton.
+ */
+ @Test
+ public void testGetRulesFromMultipleSuppressionFiles() throws Exception {
+ final int rulesInCoreFile = getNumberOfRulesLoadedInCoreFile();
+
+ // GIVEN suppression rules from one file
+ final int rulesInFirstFile = getNumberOfRulesLoadedFromPath(SUPPRESSIONS_FILE) - rulesInCoreFile;
+
+ // AND suppression rules from another file
+ final int rulesInSecondFile = getNumberOfRulesLoadedFromPath(OTHER_SUPPRESSIONS_FILE) - rulesInCoreFile;
+
+ // WHEN initializing with both suppression files
+ final String[] suppressionFiles = { SUPPRESSIONS_FILE, OTHER_SUPPRESSIONS_FILE };
+ Settings.setArrayIfNotEmpty(KEYS.SUPPRESSION_FILE, suppressionFiles);
instance.initialize();
- int expCount = 5;
- int currentSize = instance.getRules().size();
- assertTrue(expCount <= currentSize);
+
+ // THEN rules from both files were loaded
+ final int expectedSize = rulesInFirstFile + rulesInSecondFile + rulesInCoreFile;
+ assertThat("Expected suppressions from both files", instance.getRules().size(), is(expectedSize));
}
@Test(expected = InitializationException.class)
@@ -90,15 +112,33 @@ public class AbstractSuppressionAnalyzerTest extends BaseTest {
instance.initialize();
}
- private void setSupressionFileFromURL() throws Exception {
- try {
- final String uri = this.getClass().getClassLoader().getResource("suppressions.xml").toURI().toURL().toString();
- Settings.setString(Settings.KEYS.SUPPRESSION_FILE, uri);
- } catch (URISyntaxException ex) {
- LoggerFactory.getLogger(AbstractSuppressionAnalyzerTest.class).error("", ex);
- } catch (MalformedURLException ex) {
- LoggerFactory.getLogger(AbstractSuppressionAnalyzerTest.class).error("", ex);
- }
+ /**
+ * Return the number of rules that are loaded from the core suppression file.
+ *
+ * @return the number of rules defined in the core suppresion file.
+ * @throws Exception if loading the rules fails.
+ */
+ private int getNumberOfRulesLoadedInCoreFile() throws Exception {
+ Settings.removeProperty(KEYS.SUPPRESSION_FILE);
+
+ final AbstractSuppressionAnalyzerImpl coreFileAnalyzer = new AbstractSuppressionAnalyzerImpl();
+ coreFileAnalyzer.initialize();
+ return coreFileAnalyzer.getRules().size();
+ }
+
+ /**
+ * Load a file into the {@link AbstractSuppressionAnalyzer} and return the number of rules loaded.
+ *
+ * @param path the path to load.
+ * @return the number of rules that were loaded (including the core rules).
+ * @throws Exception if loading the rules fails.
+ */
+ private int getNumberOfRulesLoadedFromPath(final String path) throws Exception {
+ Settings.setString(KEYS.SUPPRESSION_FILE, path);
+
+ final AbstractSuppressionAnalyzerImpl fileAnalyzer = new AbstractSuppressionAnalyzerImpl();
+ fileAnalyzer.initialize();
+ return fileAnalyzer.getRules().size();
}
public class AbstractSuppressionAnalyzerImpl extends AbstractSuppressionAnalyzer {
diff --git a/dependency-check-core/src/test/resources/other-suppressions.xml b/dependency-check-core/src/test/resources/other-suppressions.xml
new file mode 100644
index 000000000..f0b4fad7b
--- /dev/null
+++ b/dependency-check-core/src/test/resources/other-suppressions.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+ ^com\.fasterxml\.jackson.*:.*:.*$
+ cpe:/a:fasterxml:jackson
+
+
diff --git a/dependency-check-maven/src/it/730-multiple-suppression-files/pom.xml b/dependency-check-maven/src/it/730-multiple-suppression-files/pom.xml
index 8a534171e..1b64e9518 100644
--- a/dependency-check-maven/src/it/730-multiple-suppression-files/pom.xml
+++ b/dependency-check-maven/src/it/730-multiple-suppression-files/pom.xml
@@ -44,8 +44,8 @@ Copyright (c) 2017 The OWASP Foundation. All Rights Reserved.
dependency-check-maven
- ${project.basedir}/test-suppression1.xml
- ${project.basedir}/test-suppression2.xml
+ ${project.basedir}/test-suppression1.xml
+ ${project.basedir}/test-suppression2.xml
diff --git a/dependency-check-maven/src/site/markdown/configuration.md b/dependency-check-maven/src/site/markdown/configuration.md
index 08dfab16c..e0e7b38a8 100644
--- a/dependency-check-maven/src/site/markdown/configuration.md
+++ b/dependency-check-maven/src/site/markdown/configuration.md
@@ -28,7 +28,7 @@ skipRuntimeScope | Skip analysis for artifacts with Runtime Scope.
skipSystemScope | Skip analysis for artifacts with System Scope. | false
skipTestScope | Skip analysis for artifacts with Test Scope. | true
skipArtifactType | A regular expression used to filter/skip artifact types. |
-suppressionFile | The file path to the XML suppression file \- used to suppress [false positives](../general/suppression.html). |
+suppressionFiles | The file paths to the XML suppression files \- used to suppress [false positives](../general/suppression.html). |
hintsFile | The file path to the XML hints file \- used to resolve [false negatives](../general/hints.html). |
enableExperimental | Enable the [experimental analyzers](../analyzers/index.html). If not enabled the experimental analyzers (see below) will not be loaded or used. | false
diff --git a/dependency-check-maven/src/site/markdown/index.md.vm b/dependency-check-maven/src/site/markdown/index.md.vm
index 6490307fe..43815f4ab 100644
--- a/dependency-check-maven/src/site/markdown/index.md.vm
+++ b/dependency-check-maven/src/site/markdown/index.md.vm
@@ -204,3 +204,39 @@ Update the local cache of the NVD data from NIST without analyzing the dependenc
...
```
+
+$H$H$H Example 7:
+Suppress false positives using multiple suppression files (E.g. a company-wide suppression file and a local project file).
+
+```xml
+
+ ...
+
+ ...
+
+ ...
+
+ org.owasp
+ dependency-check-maven
+ ${project.version}
+
+
+ http://example.org/suppression.xml
+ project-suppression.xml
+
+
+
+
+
+ check
+
+
+
+
+ ...
+
+ ...
+
+ ...
+
+```