diff --git a/build-reporting/pom.xml b/build-reporting/pom.xml
index 7d869e274..e5705b3f5 100644
--- a/build-reporting/pom.xml
+++ b/build-reporting/pom.xml
@@ -20,7 +20,7 @@ Copyright (c) 2017 - Jeremy Long. All Rights Reserved.
org.owasp
dependency-check-parent
- 3.0.3-SNAPSHOT
+ 3.1.0-SNAPSHOT
Dependency-Check Build-Reporting
build-reporting
diff --git a/dependency-check-ant/pom.xml b/dependency-check-ant/pom.xml
index 539ddedcf..eb46c51ed 100644
--- a/dependency-check-ant/pom.xml
+++ b/dependency-check-ant/pom.xml
@@ -20,7 +20,7 @@ Copyright (c) 2013 - Jeremy Long. All Rights Reserved.
org.owasp
dependency-check-parent
- 3.0.3-SNAPSHOT
+ 3.1.0-SNAPSHOT
dependency-check-ant
diff --git a/dependency-check-cli/pom.xml b/dependency-check-cli/pom.xml
index e7b89ce9d..869fbff83 100644
--- a/dependency-check-cli/pom.xml
+++ b/dependency-check-cli/pom.xml
@@ -20,7 +20,7 @@ Copyright (c) 2012 - Jeremy Long. All Rights Reserved.
org.owasp
dependency-check-parent
- 3.0.3-SNAPSHOT
+ 3.1.0-SNAPSHOT
dependency-check-cli
diff --git a/dependency-check-core/pom.xml b/dependency-check-core/pom.xml
index be1c2cd6e..e56a04adb 100644
--- a/dependency-check-core/pom.xml
+++ b/dependency-check-core/pom.xml
@@ -20,7 +20,7 @@ Copyright (c) 2012 Jeremy Long. All Rights Reserved.
org.owasp
dependency-check-parent
- 3.0.3-SNAPSHOT
+ 3.1.0-SNAPSHOT
dependency-check-core
@@ -164,6 +164,10 @@ Copyright (c) 2012 Jeremy Long. All Rights Reserved.
+
+ com.vdurmont
+ semver4j
+
joda-time
diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/Engine.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/Engine.java
index 41219c2aa..09a0066d9 100644
--- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/Engine.java
+++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/Engine.java
@@ -1038,6 +1038,15 @@ public class Engine implements FileFilter, AutoCloseable {
return settings;
}
+ /**
+ * Returns the mode of the engine.
+ *
+ * @return the mode of the engine
+ */
+ public Mode getMode() {
+ return mode;
+ }
+
/**
* Adds a file type analyzer. This has been added solely to assist in unit
* testing the Engine.
diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/AbstractAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/AbstractAnalyzer.java
index e208803a0..87c99d558 100644
--- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/AbstractAnalyzer.java
+++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/AbstractAnalyzer.java
@@ -85,6 +85,14 @@ public abstract class AbstractAnalyzer implements Analyzer {
@Override
public void initialize(Settings settings) {
this.settings = settings;
+ final String key = getAnalyzerEnabledSettingKey();
+ try {
+ this.setEnabled(settings.getBoolean(key, true));
+ } catch (InvalidSettingException ex) {
+ final String msg = String.format("Invalid setting for property '%s'", key);
+ LOGGER.warn(msg);
+ LOGGER.debug(msg, ex);
+ }
}
/**
@@ -95,15 +103,6 @@ public abstract class AbstractAnalyzer implements Analyzer {
*/
@Override
public final void prepare(Engine engine) throws InitializationException {
- final String key = getAnalyzerEnabledSettingKey();
- try {
- this.setEnabled(settings.getBoolean(key, true));
- } catch (InvalidSettingException ex) {
- final String msg = String.format("Invalid setting for property '%s'", key);
- LOGGER.warn(msg);
- LOGGER.debug(msg, ex);
- }
-
if (isEnabled()) {
prepareAnalyzer(engine);
} else {
diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/AbstractNpmAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/AbstractNpmAnalyzer.java
new file mode 100644
index 000000000..c88b22540
--- /dev/null
+++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/AbstractNpmAnalyzer.java
@@ -0,0 +1,291 @@
+/*
+ * 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) 2017 Steve Springett. All Rights Reserved.
+ */
+package org.owasp.dependencycheck.analyzer;
+
+import org.owasp.dependencycheck.Engine;
+import org.owasp.dependencycheck.dependency.Confidence;
+import org.owasp.dependencycheck.dependency.Dependency;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
+import javax.annotation.concurrent.ThreadSafe;
+import javax.json.Json;
+import javax.json.JsonArray;
+import javax.json.JsonObject;
+import javax.json.JsonObjectBuilder;
+import javax.json.JsonString;
+import javax.json.JsonValue;
+import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
+import org.owasp.dependencycheck.dependency.EvidenceType;
+import org.owasp.dependencycheck.utils.Checksum;
+
+/**
+ * An abstract NPM analyzer that contains common methods for concrete
+ * implementations.
+ *
+ * @author Steve Springett
+ */
+@ThreadSafe
+public abstract class AbstractNpmAnalyzer extends AbstractFileTypeAnalyzer {
+
+ /**
+ * The logger.
+ */
+ private static final Logger LOGGER = LoggerFactory.getLogger(AbstractNpmAnalyzer.class);
+
+ /**
+ * A descriptor for the type of dependencies processed or added by this
+ * analyzer.
+ */
+ public static final String NPM_DEPENDENCY_ECOSYSTEM = "npm";
+ /**
+ * The file name to scan.
+ */
+ private static final String PACKAGE_JSON = "package.json";
+
+ /**
+ * Determines if the file can be analyzed by the analyzer.
+ *
+ * @param pathname the path to the file
+ * @return true if the file can be analyzed by the given analyzer; otherwise
+ * false
+ */
+ @Override
+ public boolean accept(File pathname) {
+ boolean accept = super.accept(pathname);
+ if (accept) {
+ try {
+ accept |= shouldProcess(pathname);
+ } catch (AnalysisException ex) {
+ throw new RuntimeException(ex.getMessage(), ex.getCause());
+ }
+ }
+
+ return accept;
+ }
+
+ /**
+ * Determines if the path contains "/node_modules/" (i.e. it is a child
+ * module. This analyzer does not scan child modules.
+ *
+ * @param pathname the path to test
+ * @return true if the path does not contain "/node_modules/"
+ * @throws AnalysisException thrown if the canonical path cannot be obtained
+ * from the given file
+ */
+ protected boolean shouldProcess(File pathname) throws AnalysisException {
+ try {
+ // Do not scan the node_modules directory
+ if (pathname.getCanonicalPath().contains(File.separator + "node_modules" + File.separator)) {
+ LOGGER.debug("Skipping analysis of node module: " + pathname.getCanonicalPath());
+ return false;
+ }
+ } catch (IOException ex) {
+ throw new AnalysisException("Unable to process dependency", ex);
+ }
+ return true;
+ }
+
+ /**
+ * Construct a dependency object.
+ *
+ * @param dependency the parent dependency
+ * @param name the name of the dependency to create
+ * @param version the version of the dependency to create
+ * @param scope the scope of the dependency being created
+ * @return the generated dependency
+ */
+ protected Dependency createDependency(Dependency dependency, String name, String version, String scope) {
+ final Dependency nodeModule = new Dependency(new File(dependency.getActualFile() + "?" + name), true);
+ nodeModule.setEcosystem(NPM_DEPENDENCY_ECOSYSTEM);
+ //this is virtual - the sha1 is purely for the hyperlink in the final html report
+ nodeModule.setSha1sum(Checksum.getSHA1Checksum(String.format("%s:%s", name, version)));
+ nodeModule.setMd5sum(Checksum.getMD5Checksum(String.format("%s:%s", name, version)));
+ nodeModule.addEvidence(EvidenceType.PRODUCT, "package.json", "name", name, Confidence.HIGHEST);
+ nodeModule.addEvidence(EvidenceType.VENDOR, "package.json", "name", name, Confidence.HIGH);
+ nodeModule.addEvidence(EvidenceType.VERSION, "package.json", "version", version, Confidence.HIGHEST);
+ nodeModule.addProjectReference(dependency.getName() + ": " + scope);
+ nodeModule.setName(name);
+ nodeModule.setVersion(version);
+ nodeModule.addIdentifier("npm", String.format("%s:%s", name, version), null, Confidence.HIGHEST);
+ return nodeModule;
+ }
+
+ /**
+ * Processes a part of package.json (as defined by JsonArray) and update the
+ * specified dependency with relevant info.
+ *
+ * @param engine the dependency-check engine
+ * @param dependency the Dependency to update
+ * @param jsonArray the jsonArray to parse
+ * @param depType the dependency type
+ */
+ protected void processPackage(Engine engine, Dependency dependency, JsonArray jsonArray, String depType) {
+ final JsonObjectBuilder builder = Json.createObjectBuilder();
+ for (JsonString str : jsonArray.getValuesAs(JsonString.class)) {
+ builder.add(str.toString(), "");
+ }
+ final JsonObject jsonObject = builder.build();
+ processPackage(engine, dependency, jsonObject, depType);
+ }
+
+ /**
+ * Processes a part of package.json (as defined by JsonObject) and update
+ * the specified dependency with relevant info.
+ *
+ * @param engine the dependency-check engine
+ * @param dependency the Dependency to update
+ * @param jsonObject the jsonObject to parse
+ * @param depType the dependency type
+ */
+ protected void processPackage(Engine engine, Dependency dependency, JsonObject jsonObject, String depType) {
+ for (int i = 0; i < jsonObject.size(); i++) {
+ for (Map.Entry entry : jsonObject.entrySet()) {
+
+ final String name = entry.getKey();
+ String version = "";
+ if (entry.getValue() != null && entry.getValue().getValueType() == JsonValue.ValueType.STRING) {
+ version = ((JsonString) entry.getValue()).getString();
+ }
+ final Dependency existing = findDependency(engine, name, version);
+ if (existing == null) {
+ final Dependency nodeModule = createDependency(dependency, name, version, depType);
+ engine.addDependency(nodeModule);
+ } else {
+ existing.addProjectReference(dependency.getName() + ": " + depType);
+ }
+ }
+ }
+ }
+
+ /**
+ * Adds information to an evidence collection from the node json
+ * configuration.
+ *
+ * @param dep the dependency to add the evidence
+ * @param t the type of evidence to add
+ * @param json information from node.js
+ * @return the actual string set into evidence
+ * @param key the key to obtain the data from the json information
+ */
+ private static String addToEvidence(Dependency dep, EvidenceType t, JsonObject json, String key) {
+ String evidenceStr = null;
+ if (json.containsKey(key)) {
+ final JsonValue value = json.get(key);
+ if (value instanceof JsonString) {
+ evidenceStr = ((JsonString) value).getString();
+ dep.addEvidence(t, PACKAGE_JSON, key, evidenceStr, Confidence.HIGHEST);
+ } else if (value instanceof JsonObject) {
+ final JsonObject jsonObject = (JsonObject) value;
+ for (final Map.Entry entry : jsonObject.entrySet()) {
+ final String property = entry.getKey();
+ final JsonValue subValue = entry.getValue();
+ if (subValue instanceof JsonString) {
+ evidenceStr = ((JsonString) subValue).getString();
+ dep.addEvidence(t, PACKAGE_JSON,
+ String.format("%s.%s", key, property),
+ evidenceStr,
+ Confidence.HIGHEST);
+ } else {
+ LOGGER.warn("JSON sub-value not string as expected: {}", subValue);
+ }
+ }
+ } else {
+ LOGGER.warn("JSON value not string or JSON object as expected: {}", value);
+ }
+ }
+ return evidenceStr;
+ }
+
+ /**
+ * Locates the dependency from the list of dependencies that have been
+ * scanned by the engine.
+ *
+ * @param engine the dependency-check engine
+ * @param name the name of the dependency to find
+ * @param version the version of the dependency to find
+ * @return the identified dependency; otherwise null
+ */
+ protected Dependency findDependency(Engine engine, String name, String version) {
+ for (Dependency d : engine.getDependencies()) {
+ if (NPM_DEPENDENCY_ECOSYSTEM.equals(d.getEcosystem()) && name.equals(d.getName()) && version != null && d.getVersion() != null) {
+ final String dependencyVersion = d.getVersion();
+ if (DependencyBundlingAnalyzer.npmVersionsMatch(version, dependencyVersion)) {
+ return d;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Collects evidence from the given JSON for the associated dependency.
+ *
+ * @param json the JSON that contains the evidence to collect
+ * @param dependency the dependency to add the evidence too
+ */
+ public void gatherEvidence(final JsonObject json, Dependency dependency) {
+ if (json.containsKey("name")) {
+ final Object value = json.get("name");
+ if (value instanceof JsonString) {
+ final String valueString = ((JsonString) value).getString();
+ dependency.setName(valueString);
+ dependency.setPackagePath(valueString);
+ dependency.addEvidence(EvidenceType.PRODUCT, PACKAGE_JSON, "name", valueString, Confidence.HIGHEST);
+ dependency.addEvidence(EvidenceType.VENDOR, PACKAGE_JSON, "name", valueString, Confidence.HIGH);
+ } else {
+ LOGGER.warn("JSON value not string as expected: {}", value);
+ }
+ }
+ final String desc = addToEvidence(dependency, EvidenceType.PRODUCT, json, "description");
+ dependency.setDescription(desc);
+ addToEvidence(dependency, EvidenceType.VENDOR, json, "author");
+ final String version = addToEvidence(dependency, EvidenceType.VERSION, json, "version");
+ if (version != null) {
+ dependency.setVersion(version);
+ dependency.addIdentifier("npm", String.format("%s:%s", dependency.getName(), version), null, Confidence.HIGHEST);
+ }
+
+ // Adds the license if defined in package.json
+ if (json.containsKey("license")) {
+ final Object value = json.get("license");
+ if (value instanceof JsonString) {
+ dependency.setLicense(json.getString("license"));
+ } else if (value instanceof JsonArray) {
+ final JsonArray array = (JsonArray) value;
+ final StringBuilder sb = new StringBuilder();
+ boolean addComma = false;
+ for (int x = 0; x < array.size(); x++) {
+ if (!array.isNull(x)) {
+ if (addComma) {
+ sb.append(", ");
+ } else {
+ addComma = true;
+ }
+ sb.append(array.getString(x));
+ }
+ }
+ dependency.setLicense(sb.toString());
+ } else {
+ dependency.setLicense(json.getJsonObject("license").getString("type"));
+ }
+ }
+ }
+}
diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/CPEAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/CPEAnalyzer.java
index ae2a55d35..34685d6bf 100644
--- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/CPEAnalyzer.java
+++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/CPEAnalyzer.java
@@ -21,6 +21,7 @@ import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
@@ -106,6 +107,12 @@ public class CPEAnalyzer extends AbstractAnalyzer {
* The CVE Database.
*/
private CveDB cve;
+ /**
+ * The list of ecosystems to skip during analysis. These are skipped because
+ * there is generally a more accurate vulnerability analyzer in the
+ * pipeline.
+ */
+ private List skipEcosystems;
/**
* Returns the name of this analyzer.
@@ -136,6 +143,7 @@ public class CPEAnalyzer extends AbstractAnalyzer {
*/
@Override
public void prepareAnalyzer(Engine engine) throws InitializationException {
+ super.prepareAnalyzer(engine);
try {
this.open(engine.getDatabase());
} catch (IOException ex) {
@@ -145,6 +153,13 @@ public class CPEAnalyzer extends AbstractAnalyzer {
LOGGER.debug("Exception accessing the database", ex);
throw new InitializationException("An exception occurred accessing the database", ex);
}
+ final String[] tmp = engine.getSettings().getArray(Settings.KEYS.ECOSYSTEM_SKIP_CPEANALYZER);
+ if (tmp == null) {
+ skipEcosystems = new ArrayList<>();
+ } else {
+ LOGGER.info("Skipping CPE Analysis for {}", tmp);
+ skipEcosystems = Arrays.asList(tmp);
+ }
}
/**
@@ -525,6 +540,9 @@ public class CPEAnalyzer extends AbstractAnalyzer {
*/
@Override
protected void analyzeDependency(Dependency dependency, Engine engine) throws AnalysisException {
+ if (skipEcosystems.contains(dependency.getEcosystem())) {
+ return;
+ }
try {
determineCPE(dependency);
} catch (CorruptIndexException ex) {
diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/DependencyBundlingAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/DependencyBundlingAnalyzer.java
index 30125e419..c6cceee9d 100644
--- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/DependencyBundlingAnalyzer.java
+++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/DependencyBundlingAnalyzer.java
@@ -17,6 +17,9 @@
*/
package org.owasp.dependencycheck.analyzer;
+import com.vdurmont.semver4j.Semver;
+import com.vdurmont.semver4j.Semver.SemverType;
+import com.vdurmont.semver4j.SemverException;
import java.io.File;
import java.util.Set;
import java.util.regex.Matcher;
@@ -135,6 +138,16 @@ public class DependencyBundlingAnalyzer extends AbstractDependencyComparingAnaly
mergeDependencies(nextDependency, dependency, dependenciesToRemove);
return true; //since we merged into the next dependency - skip forward to the next in mainIterator
}
+ } else if (ecoSystemIs(AbstractNpmAnalyzer.NPM_DEPENDENCY_ECOSYSTEM, dependency, nextDependency)
+ && namesAreEqual(dependency, nextDependency)
+ && npmVersionsMatch(dependency.getVersion(), nextDependency.getVersion())) {
+
+ if (!dependency.isVirtual()) {
+ DependencyMergingAnalyzer.mergeDependencies(dependency, nextDependency, dependenciesToRemove);
+ } else {
+ DependencyMergingAnalyzer.mergeDependencies(nextDependency, dependency, dependenciesToRemove);
+ return true;
+ }
}
return false;
}
@@ -149,7 +162,8 @@ public class DependencyBundlingAnalyzer extends AbstractDependencyComparingAnaly
* removed from the main analysis loop, this function adds to this
* collection
*/
- private void mergeDependencies(final Dependency dependency, final Dependency relatedDependency, final Set dependenciesToRemove) {
+ public static void mergeDependencies(final Dependency dependency,
+ final Dependency relatedDependency, final Set dependenciesToRemove) {
dependency.addRelatedDependency(relatedDependency);
for (Dependency d : relatedDependency.getRelatedDependencies()) {
dependency.addRelatedDependency(d);
@@ -158,7 +172,9 @@ public class DependencyBundlingAnalyzer extends AbstractDependencyComparingAnaly
if (dependency.getSha1sum().equals(relatedDependency.getSha1sum())) {
dependency.addAllProjectReferences(relatedDependency.getProjectReferences());
}
- dependenciesToRemove.add(relatedDependency);
+ if (dependenciesToRemove != null) {
+ dependenciesToRemove.add(relatedDependency);
+ }
}
/**
@@ -452,4 +468,105 @@ public class DependencyBundlingAnalyzer extends AbstractDependencyComparingAnaly
return filePath != null && filePath.matches(".*\\.(ear|war)[\\\\/].*");
}
+ /**
+ * Determine if the dependency ecosystem is equal in the given dependencies.
+ *
+ * @param ecoSystem the ecosystem to validate against
+ * @param dependency a dependency to compare
+ * @param nextDependency a dependency to compare
+ * @return true if the ecosystem is equal in both dependencies; otherwise
+ * false
+ */
+ private boolean ecoSystemIs(String ecoSystem, Dependency dependency, Dependency nextDependency) {
+ return ecoSystem.equals(dependency.getEcosystem()) && ecoSystem.equals(nextDependency.getEcosystem());
+ }
+
+ /**
+ * Determine if the dependency name is equal in the given dependencies.
+ *
+ * @param dependency a dependency to compare
+ * @param nextDependency a dependency to compare
+ * @return true if the name is equal in both dependencies; otherwise false
+ */
+ private boolean namesAreEqual(Dependency dependency, Dependency nextDependency) {
+ return dependency.getName() != null && dependency.getName().equals(nextDependency.getName());
+ }
+
+ /**
+ * Determine if the dependency version is equal in the given dependencies.
+ * This method attempts to evaluate version range checks.
+ *
+ * @param current a dependency version to compare
+ * @param next a dependency version to compare
+ * @return true if the version is equal in both dependencies; otherwise
+ * false
+ */
+ public static boolean npmVersionsMatch(String current, String next) {
+ String left = current;
+ String right = next;
+ if (left == null || right == null) {
+ return false;
+ }
+ if (left.equals(right) || "*".equals(left) || "*".equals(right)) {
+ return true;
+ }
+ if (left.contains(" ")) { // we have a version string from package.json
+ if (right.contains(" ")) { // we can't evaluate this ">=1.5.4 <2.0.0" vs "2 || 3"
+ return false;
+ }
+ if (!right.matches("^\\d.*$")) {
+ right = stripLeadingNonNumeric(right);
+ if (right == null) {
+ return false;
+ }
+ }
+ try {
+ final Semver v = new Semver(right, SemverType.NPM);
+ return v.satisfies(left);
+ } catch (SemverException ex) {
+ LOGGER.trace("ignore", ex);
+ }
+ } else {
+ if (!left.matches("^\\d.*$")) {
+ left = stripLeadingNonNumeric(left);
+ if (left == null) {
+ return false;
+ }
+ }
+ try {
+ Semver v = new Semver(left, SemverType.NPM);
+ if (v.satisfies(right)) {
+ return true;
+ }
+ if (!right.contains((" "))) {
+ left = current;
+ right = stripLeadingNonNumeric(right);
+ if (right != null) {
+ v = new Semver(right, SemverType.NPM);
+ return v.satisfies(left);
+ }
+ }
+ } catch (SemverException ex) {
+ LOGGER.trace("ignore", ex);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Strips leading non-numeric values from the start of the string. If no
+ * numbers are present this will return null.
+ *
+ * @param str the string to modify
+ * @return the string without leading non-numeric characters
+ */
+ private static String stripLeadingNonNumeric(String str) {
+ for (int x = 0; x < str.length(); x++) {
+ if (Character.isDigit(str.codePointAt(x))) {
+ return str.substring(x);
+ }
+ }
+ return null;
+ }
+
}
diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/DependencyMergingAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/DependencyMergingAnalyzer.java
index 62141b2c3..fb98e7145 100644
--- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/DependencyMergingAnalyzer.java
+++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/DependencyMergingAnalyzer.java
@@ -120,7 +120,8 @@ public class DependencyMergingAnalyzer extends AbstractDependencyComparingAnalyz
* removed from the main analysis loop, this function adds to this
* collection
*/
- private void mergeDependencies(final Dependency dependency, final Dependency relatedDependency, final Set dependenciesToRemove) {
+ public static void mergeDependencies(final Dependency dependency, final Dependency relatedDependency,
+ final Set dependenciesToRemove) {
LOGGER.debug("Merging '{}' into '{}'", relatedDependency.getFilePath(), dependency.getFilePath());
dependency.addRelatedDependency(relatedDependency);
for (Evidence e : relatedDependency.getEvidence(EvidenceType.VENDOR)) {
@@ -137,10 +138,10 @@ public class DependencyMergingAnalyzer extends AbstractDependencyComparingAnalyz
dependency.addRelatedDependency(d);
relatedDependency.removeRelatedDependencies(d);
}
- if (dependency.getSha1sum().equals(relatedDependency.getSha1sum())) {
- dependency.addAllProjectReferences(relatedDependency.getProjectReferences());
+ dependency.addAllProjectReferences(relatedDependency.getProjectReferences());
+ if (dependenciesToRemove != null) {
+ dependenciesToRemove.add(relatedDependency);
}
- dependenciesToRemove.add(relatedDependency);
}
/**
diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/NodePackageAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/NodePackageAnalyzer.java
index f9b83008d..f5821d8de 100644
--- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/NodePackageAnalyzer.java
+++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/NodePackageAnalyzer.java
@@ -20,7 +20,6 @@ package org.owasp.dependencycheck.analyzer;
import org.apache.commons.io.FileUtils;
import org.owasp.dependencycheck.Engine;
import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
-import org.owasp.dependencycheck.dependency.Confidence;
import org.owasp.dependencycheck.dependency.Dependency;
import org.owasp.dependencycheck.utils.FileFilterBuilder;
import org.owasp.dependencycheck.utils.Settings;
@@ -30,16 +29,22 @@ import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.List;
import java.util.Map;
import javax.annotation.concurrent.ThreadSafe;
import javax.json.Json;
import javax.json.JsonException;
import javax.json.JsonObject;
import javax.json.JsonReader;
-import javax.json.JsonString;
import javax.json.JsonValue;
-import org.owasp.dependencycheck.exception.InitializationException;
+import org.owasp.dependencycheck.Engine.Mode;
+import org.owasp.dependencycheck.dependency.Confidence;
import org.owasp.dependencycheck.dependency.EvidenceType;
+import org.owasp.dependencycheck.exception.InitializationException;
+import org.owasp.dependencycheck.utils.Checksum;
+import org.owasp.dependencycheck.utils.InvalidSettingException;
/**
* Used to analyze Node Package Manager (npm) package.json files, and collect
@@ -48,8 +53,7 @@ import org.owasp.dependencycheck.dependency.EvidenceType;
* @author Dale Visser
*/
@ThreadSafe
-@Retired
-public class NodePackageAnalyzer extends AbstractFileTypeAnalyzer {
+public class NodePackageAnalyzer extends AbstractNpmAnalyzer {
/**
* The logger.
@@ -59,7 +63,7 @@ public class NodePackageAnalyzer extends AbstractFileTypeAnalyzer {
* A descriptor for the type of dependencies processed or added by this
* analyzer.
*/
- public static final String DEPENDENCY_ECOSYSTEM = "npm";
+ public static final String DEPENDENCY_ECOSYSTEM = NPM_DEPENDENCY_ECOSYSTEM;
/**
* The name of the analyzer.
*/
@@ -73,10 +77,19 @@ public class NodePackageAnalyzer extends AbstractFileTypeAnalyzer {
*/
public static final String PACKAGE_JSON = "package.json";
/**
- * Filter that detects files named "package.json".
+ * The file name to scan.
+ */
+ public static final String PACKAGE_LOCK_JSON = "package-lock.json";
+ /**
+ * The file name to scan.
+ */
+ public static final String SHRINKWRAP_JSON = "npm-shrinkwrap.json";
+ /**
+ * Filter that detects files named "package-lock.json" or
+ * "npm-shrinkwrap.json".
*/
private static final FileFilter PACKAGE_JSON_FILTER = FileFilterBuilder.newInstance()
- .addFilenames(PACKAGE_JSON).build();
+ .addFilenames(PACKAGE_LOCK_JSON, SHRINKWRAP_JSON).build();
/**
* Returns the FileFilter
@@ -88,9 +101,35 @@ public class NodePackageAnalyzer extends AbstractFileTypeAnalyzer {
return PACKAGE_JSON_FILTER;
}
+ /**
+ * Performs validation on the configuration to ensure that the correct
+ * analyzers are in place.
+ *
+ * @param engine the dependency-check engine
+ * @throws InitializationException thrown if there is a configuration error
+ */
@Override
protected void prepareFileTypeAnalyzer(Engine engine) throws InitializationException {
- // NO-OP
+ if (engine.getMode() != Mode.EVIDENCE_COLLECTION) {
+ try {
+ final Settings settings = engine.getSettings();
+ final String[] tmp = settings.getArray(Settings.KEYS.ECOSYSTEM_SKIP_CPEANALYZER);
+ if (tmp != null) {
+ final List skipEcosystems = Arrays.asList(tmp);
+ if (skipEcosystems.contains(DEPENDENCY_ECOSYSTEM)
+ && !settings.getBoolean(Settings.KEYS.ANALYZER_NSP_PACKAGE_ENABLED)) {
+ LOGGER.debug("NodePackageAnalyzer enabled without a corresponding vulnerability analyzer");
+ final String msg = "Invalid Configuration: enabling the Node Package Analyzer without "
+ + "using the NSP Analyzer is not supported.";
+ throw new InitializationException(msg);
+ } else if (!skipEcosystems.contains(DEPENDENCY_ECOSYSTEM)) {
+ LOGGER.warn("Using the CPE Analyzer with Node.js can result in many false positives.");
+ }
+ }
+ } catch (InvalidSettingException ex) {
+ throw new InitializationException("Unable to read configuration settings", ex);
+ }
+ }
}
/**
@@ -114,10 +153,10 @@ public class NodePackageAnalyzer extends AbstractFileTypeAnalyzer {
}
/**
- * Returns the key used in the properties file to reference the analyzer's
- * enabled property.
+ * Returns the key used in the properties file to reference the enabled
+ * property for the analyzer.
*
- * @return the analyzer's enabled property setting key
+ * @return the enabled property setting key for the analyzer
*/
@Override
protected String getAnalyzerEnabledSettingKey() {
@@ -126,29 +165,31 @@ public class NodePackageAnalyzer extends AbstractFileTypeAnalyzer {
@Override
protected void analyzeDependency(Dependency dependency, Engine engine) throws AnalysisException {
- dependency.setEcosystem(DEPENDENCY_ECOSYSTEM);
- final File file = dependency.getActualFile();
- if (!file.isFile() || file.length() == 0) {
+ engine.removeDependency(dependency);
+ final File dependencyFile = dependency.getActualFile();
+ if (!dependencyFile.isFile() || dependencyFile.length() == 0 || !shouldProcess(dependencyFile)) {
return;
}
- try (JsonReader jsonReader = Json.createReader(FileUtils.openInputStream(file))) {
- final JsonObject json = jsonReader.readObject();
- if (json.containsKey("name")) {
- final Object value = json.get("name");
- if (value instanceof JsonString) {
- final String valueString = ((JsonString) value).getString();
- dependency.setName(valueString);
- dependency.addEvidence(EvidenceType.PRODUCT, PACKAGE_JSON, "name", valueString, Confidence.HIGHEST);
- dependency.addEvidence(EvidenceType.VENDOR, PACKAGE_JSON, "name_project",
- String.format("%s_project", valueString), Confidence.LOW);
- } else {
- LOGGER.warn("JSON value not string as expected: {}", value);
- }
+ final File baseDir = dependencyFile.getParentFile();
+ if (PACKAGE_LOCK_JSON.equals(dependency.getFileName())) {
+ final File shrinkwrap = new File(baseDir, SHRINKWRAP_JSON);
+ if (shrinkwrap.exists()) {
+ return;
}
- addToEvidence(dependency, EvidenceType.PRODUCT, json, "description");
- addToEvidence(dependency, EvidenceType.VENDOR, json, "author");
- final String version = addToEvidence(dependency, EvidenceType.VERSION, json, "version");
- dependency.setVersion(version);
+ }
+ final File nodeModules = new File(baseDir, "node_modules");
+ if (!nodeModules.isDirectory()) {
+ LOGGER.warn("Analyzing `{}` - however, the node_modules directory does not exist. "
+ + "Please run `npm install` prior to running dependency-check", dependencyFile.toString());
+ return;
+ }
+
+ try (JsonReader jsonReader = Json.createReader(FileUtils.openInputStream(dependencyFile))) {
+ final JsonObject json = jsonReader.readObject();
+ final String parentName = json.getString("name");
+ final String parentVersion = json.getString("version");
+ final String parentPackage = String.format("%s:%s", parentName, parentVersion);
+ processDependencies(json, baseDir, dependencyFile, parentPackage, engine);
} catch (JsonException e) {
LOGGER.warn("Failed to parse package.json file.", e);
} catch (IOException e) {
@@ -157,41 +198,74 @@ public class NodePackageAnalyzer extends AbstractFileTypeAnalyzer {
}
/**
- * Adds information to an evidence collection from the node json
- * configuration.
+ * Process the dependencies in the lock file by first parsing its
+ * dependencies and then finding the package.json for the module and adding
+ * it as a dependency.
*
- * @param dep the dependency to add the evidence
- * @param t the type of evidence to add
- * @param json information from node.js
- * @return the actual string set into evidence
- * @param key the key to obtain the data from the json information
+ * @param json the data to process
+ * @param baseDir the base directory being scanned
+ * @param rootFile the root package-lock/npm-shrinkwrap being analyzed
+ * @param parentPackage the parent package name of the current node
+ * @param engine a reference to the dependency-check engine
+ * @throws AnalysisException thrown if there is an exception
*/
- private String addToEvidence(Dependency dep, EvidenceType t, JsonObject json, String key) {
- String evidenceStr = null;
- if (json.containsKey(key)) {
- final JsonValue value = json.get(key);
- if (value instanceof JsonString) {
- evidenceStr = ((JsonString) value).getString();
- dep.addEvidence(t, PACKAGE_JSON, key, evidenceStr, Confidence.HIGHEST);
- } else if (value instanceof JsonObject) {
- final JsonObject jsonObject = (JsonObject) value;
- for (final Map.Entry entry : jsonObject.entrySet()) {
- final String property = entry.getKey();
- final JsonValue subValue = entry.getValue();
- if (subValue instanceof JsonString) {
- evidenceStr = ((JsonString) subValue).getString();
- dep.addEvidence(t, PACKAGE_JSON,
- String.format("%s.%s", key, property),
- evidenceStr,
- Confidence.HIGHEST);
- } else {
- LOGGER.warn("JSON sub-value not string as expected: {}", subValue);
- }
+ private void processDependencies(JsonObject json, File baseDir, File rootFile,
+ String parentPackage, Engine engine) throws AnalysisException {
+ if (json.containsKey("dependencies")) {
+ final JsonObject deps = json.getJsonObject("dependencies");
+ for (Map.Entry entry : deps.entrySet()) {
+ final JsonObject jo = (JsonObject) entry.getValue();
+ final String name = entry.getKey();
+ final String version = jo.getString("version");
+ final File base = Paths.get(baseDir.getPath(), "node_modules", name).toFile();
+ final File f = new File(base, PACKAGE_JSON);
+
+ if (jo.containsKey("dependencies")) {
+ final String subPackageName = String.format("%s/%s:%s", parentPackage, name, version);
+ processDependencies(jo, base, rootFile, subPackageName, engine);
+ }
+
+ Dependency child;
+ if (f.exists()) {
+ //TOOD - we should use the integrity value instead of calculating the SHA1/MD5
+ child = new Dependency(f);
+ try (JsonReader jr = Json.createReader(FileUtils.openInputStream(f))) {
+ final JsonObject childJson = jr.readObject();
+ gatherEvidence(childJson, child);
+
+ } catch (JsonException e) {
+ LOGGER.warn("Failed to parse package.json file from dependency.", e);
+ } catch (IOException e) {
+ throw new AnalysisException("Problem occurred while reading dependency file.", e);
+ }
+ } else {
+ LOGGER.warn("Unable to find node module: {}", f.toString());
+ child = new Dependency(rootFile, true);
+ //TOOD - we should use the integrity value instead of calculating the SHA1/MD5
+ child.setSha1sum(Checksum.getSHA1Checksum(String.format("%s:%s", name, version)));
+ child.setMd5sum(Checksum.getMD5Checksum(String.format("%s:%s", name, version)));
+ child.addEvidence(EvidenceType.VENDOR, rootFile.getName(), "name", name, Confidence.HIGHEST);
+ child.addEvidence(EvidenceType.PRODUCT, rootFile.getName(), "name", name, Confidence.HIGHEST);
+ child.addEvidence(EvidenceType.VERSION, rootFile.getName(), "version", version, Confidence.HIGHEST);
+ }
+ child.setName(name);
+ child.setVersion(version);
+ child.addProjectReference(parentPackage);
+ child.setEcosystem(DEPENDENCY_ECOSYSTEM);
+
+ final Dependency existing = findDependency(engine, name, version);
+ if (existing != null) {
+ if (existing.isVirtual()) {
+ DependencyMergingAnalyzer.mergeDependencies(child, existing, null);
+ engine.removeDependency(existing);
+ engine.addDependency(child);
+ } else {
+ DependencyBundlingAnalyzer.mergeDependencies(existing, child, null);
+ }
+ } else {
+ engine.addDependency(child);
}
- } else {
- LOGGER.warn("JSON value not string or JSON object as expected: {}", value);
}
}
- return evidenceStr;
}
}
diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/NspAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/NspAnalyzer.java
index da33c61a6..41b6a36c6 100644
--- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/NspAnalyzer.java
+++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/NspAnalyzer.java
@@ -23,9 +23,7 @@ import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
import org.owasp.dependencycheck.data.nsp.Advisory;
import org.owasp.dependencycheck.data.nsp.NspSearch;
import org.owasp.dependencycheck.data.nsp.SanitizePackage;
-import org.owasp.dependencycheck.dependency.Confidence;
import org.owasp.dependencycheck.dependency.Dependency;
-import org.owasp.dependencycheck.dependency.Identifier;
import org.owasp.dependencycheck.dependency.Vulnerability;
import org.owasp.dependencycheck.dependency.VulnerableSoftware;
import org.owasp.dependencycheck.utils.FileFilterBuilder;
@@ -39,18 +37,14 @@ import java.net.MalformedURLException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
-import java.util.Map;
import javax.annotation.concurrent.ThreadSafe;
import javax.json.Json;
-import javax.json.JsonArray;
import javax.json.JsonException;
import javax.json.JsonObject;
import javax.json.JsonObjectBuilder;
import javax.json.JsonReader;
-import javax.json.JsonString;
-import javax.json.JsonValue;
-import org.owasp.dependencycheck.dependency.EvidenceType;
import org.owasp.dependencycheck.exception.InitializationException;
+import org.owasp.dependencycheck.utils.InvalidSettingException;
import org.owasp.dependencycheck.utils.URLConnectionFailureException;
/**
@@ -60,7 +54,7 @@ import org.owasp.dependencycheck.utils.URLConnectionFailureException;
* @author Steve Springett
*/
@ThreadSafe
-public class NspAnalyzer extends AbstractFileTypeAnalyzer {
+public class NspAnalyzer extends AbstractNpmAnalyzer {
/**
* The logger.
@@ -71,7 +65,11 @@ public class NspAnalyzer extends AbstractFileTypeAnalyzer {
* The default URL to the NSP check API.
*/
public static final String DEFAULT_URL = "https://api.nodesecurity.io/check";
-
+ /**
+ * A descriptor for the type of dependencies processed or added by this
+ * analyzer.
+ */
+ public static final String DEPENDENCY_ECOSYSTEM = NPM_DEPENDENCY_ECOSYSTEM;
/**
* The file name to scan.
*/
@@ -113,6 +111,16 @@ public class NspAnalyzer extends AbstractFileTypeAnalyzer {
setEnabled(false);
throw new InitializationException("The configured URL to Node Security Platform is malformed", ex);
}
+ try {
+ final Settings settings = engine.getSettings();
+ final boolean nodeEnabled = settings.getBoolean(Settings.KEYS.ANALYZER_NODE_PACKAGE_ENABLED);
+ if (!nodeEnabled) {
+ LOGGER.warn("The Node Package Analyzer has been disabled; the resulting report will only "
+ + " contain the known vulnerable dependency - not a bill of materials for the node project.");
+ }
+ } catch (InvalidSettingException ex) {
+ throw new InitializationException("Unable to read configuration settings", ex);
+ }
}
/**
@@ -136,10 +144,10 @@ public class NspAnalyzer extends AbstractFileTypeAnalyzer {
}
/**
- * Returns the key used in the properties file to reference the analyzer's
- * enabled property.x
+ * Returns the key used in the properties file to determine if the analyzer
+ * is enabled.
*
- * @return the analyzer's enabled property setting key
+ * @return the enabled property setting key for the analyzer
*/
@Override
protected String getAnalyzerEnabledSettingKey() {
@@ -148,17 +156,13 @@ public class NspAnalyzer extends AbstractFileTypeAnalyzer {
@Override
protected void analyzeDependency(Dependency dependency, Engine engine) throws AnalysisException {
+ engine.removeDependency(dependency);
final File file = dependency.getActualFile();
- if (!file.isFile() || file.length() == 0) {
+ if (!file.isFile() || file.length() == 0 || !shouldProcess(file)) {
return;
}
- try (JsonReader jsonReader = Json.createReader(FileUtils.openInputStream(file))) {
- // Do not scan the node_modules directory
- if (file.getCanonicalPath().contains(File.separator + "node_modules" + File.separator)) {
- LOGGER.debug("Skipping analysis of node module: " + file.getCanonicalPath());
- return;
- }
+ try (JsonReader jsonReader = Json.createReader(FileUtils.openInputStream(file))) {
// Retrieves the contents of package.json from the Dependency
final JsonObject packageJson = jsonReader.readObject();
@@ -192,77 +196,22 @@ public class NspAnalyzer extends AbstractFileTypeAnalyzer {
* Create a single vulnerable software object - these do not use CPEs unlike the NVD.
*/
final VulnerableSoftware vs = new VulnerableSoftware();
- //vs.setVersion(advisory.getVulnerableVersions());
- vs.setUpdate(advisory.getPatchedVersions());
+ //TODO consider changing this to available versions on the dependency
+ // - the update is a part of the version, not versions to update to
+ //vs.setUpdate(advisory.getPatchedVersions());
+
vs.setName(advisory.getModule() + ":" + advisory.getVulnerableVersions());
vuln.setVulnerableSoftware(new HashSet<>(Arrays.asList(vs)));
- // Add the vulnerability to package.json
- dependency.addVulnerability(vuln);
- }
-
- /*
- * Adds evidence about the node package itself, not any of the modules.
- */
- if (packageJson.containsKey("name")) {
- final Object value = packageJson.get("name");
- if (value instanceof JsonString) {
- final String valueString = ((JsonString) value).getString();
- dependency.addEvidence(EvidenceType.PRODUCT, PACKAGE_JSON, "name", valueString, Confidence.HIGHEST);
- dependency.addEvidence(EvidenceType.VENDOR, PACKAGE_JSON, "name_project",
- String.format("%s_project", valueString), Confidence.LOW);
+ final Dependency existing = findDependency(engine, advisory.getModule(), advisory.getVersion());
+ if (existing == null) {
+ final Dependency nodeModule = createDependency(dependency, advisory.getModule(), advisory.getVersion(), "transitive");
+ nodeModule.addVulnerability(vuln);
+ engine.addDependency(nodeModule);
} else {
- LOGGER.warn("JSON value not string as expected: {}", value);
+ existing.addVulnerability(vuln);
}
}
-
- /*
- * Processes the dependencies objects in package.json and adds all the modules as related dependencies
- */
- if (packageJson.containsKey("dependencies")) {
- final JsonObject dependencies = packageJson.getJsonObject("dependencies");
- processPackage(dependency, dependencies, "dependencies");
- }
- if (packageJson.containsKey("devDependencies")) {
- final JsonObject dependencies = packageJson.getJsonObject("devDependencies");
- processPackage(dependency, dependencies, "devDependencies");
- }
- if (packageJson.containsKey("optionalDependencies")) {
- final JsonObject dependencies = packageJson.getJsonObject("optionalDependencies");
- processPackage(dependency, dependencies, "optionalDependencies");
- }
- if (packageJson.containsKey("peerDependencies")) {
- final JsonObject dependencies = packageJson.getJsonObject("peerDependencies");
- processPackage(dependency, dependencies, "peerDependencies");
- }
- if (packageJson.containsKey("bundleDependencies")) {
- final JsonArray dependencies = packageJson.getJsonArray("bundleDependencies");
- processPackage(dependency, dependencies, "bundleDependencies");
- }
- if (packageJson.containsKey("bundledDependencies")) {
- final JsonArray dependencies = packageJson.getJsonArray("bundledDependencies");
- processPackage(dependency, dependencies, "bundledDependencies");
- }
-
- /*
- * Adds the license if defined in package.json
- */
- if (packageJson.containsKey("license")) {
- final Object value = packageJson.get("license");
- if (value instanceof JsonString) {
- dependency.setLicense(packageJson.getString("license"));
- } else {
- dependency.setLicense(packageJson.getJsonObject("license").getString("type"));
- }
- }
-
- /*
- * Adds general evidence to about the package.
- */
- addToEvidence(dependency, EvidenceType.PRODUCT, packageJson, "description");
- addToEvidence(dependency, EvidenceType.VENDOR, packageJson, "author");
- addToEvidence(dependency, EvidenceType.VERSION, packageJson, "version");
- dependency.setDisplayFileName(String.format("%s/%s", file.getParentFile().getName(), file.getName()));
} catch (URLConnectionFailureException e) {
this.setEnabled(false);
throw new AnalysisException(e.getMessage(), e);
@@ -274,98 +223,4 @@ public class NspAnalyzer extends AbstractFileTypeAnalyzer {
throw new AnalysisException(String.format("Failed to parse %s file.", file.getPath()), e);
}
}
-
- /**
- * Processes a part of package.json (as defined by JsonArray) and update the
- * specified dependency with relevant info.
- *
- * @param dependency the Dependency to update
- * @param jsonArray the jsonArray to parse
- * @param depType the dependency type
- */
- private void processPackage(Dependency dependency, JsonArray jsonArray, String depType) {
- final JsonObjectBuilder builder = Json.createObjectBuilder();
- for (JsonString str : jsonArray.getValuesAs(JsonString.class)) {
- builder.add(str.toString(), "");
- }
- final JsonObject jsonObject = builder.build();
- processPackage(dependency, jsonObject, depType);
- }
-
- /**
- * Processes a part of package.json (as defined by JsonObject) and update
- * the specified dependency with relevant info.
- *
- * @param dependency the Dependency to update
- * @param jsonObject the jsonObject to parse
- * @param depType the dependency type
- */
- private void processPackage(Dependency dependency, JsonObject jsonObject, String depType) {
- for (int i = 0; i < jsonObject.size(); i++) {
- for (Map.Entry entry : jsonObject.entrySet()) {
- /*
- * Create identifies that include the npm module and version. Since these are defined,
- * assign the highest confidence.
- */
- final Identifier moduleName = new Identifier("npm", "Module", null, entry.getKey());
- moduleName.setConfidence(Confidence.HIGHEST);
- String version = "";
- if (entry.getValue() != null && entry.getValue().getValueType() == JsonValue.ValueType.STRING) {
- version = ((JsonString) entry.getValue()).getString();
- }
- final Identifier moduleVersion = new Identifier("npm", "Version", null, version);
- moduleVersion.setConfidence(Confidence.HIGHEST);
-
- final Identifier moduleDepType = new Identifier("npm", "Scope", null, depType);
- moduleVersion.setConfidence(Confidence.HIGHEST);
-
- /*
- * Create related dependencies for each module defined in package.json. The path to the related
- * dependency will not actually exist but needs to be unique (due to the use of Set in Dependency).
- * The use of related dependencies is a way to specify the actual software BOM in package.json.
- */
- //TODO is this actually correct? or should these be transitive dependencies?
- final Dependency nodeModule = new Dependency(new File(dependency.getActualFile() + "#" + entry.getKey()), true);
- nodeModule.setDisplayFileName(entry.getKey());
- nodeModule.addIdentifier(moduleName);
- nodeModule.addIdentifier(moduleVersion);
- nodeModule.addIdentifier(moduleDepType);
- dependency.addRelatedDependency(nodeModule);
- }
- }
- }
-
- /**
- * Adds information to an evidence collection from the node json
- * configuration.
- *
- * @param dep the dependency to which the evidence will be added
- * @param type the type of evidence to be added
- * @param json information from node.js
- * @param key the key to obtain the data from the json information
- */
- private void addToEvidence(Dependency dep, EvidenceType type, JsonObject json, String key) {
- if (json.containsKey(key)) {
- final JsonValue value = json.get(key);
- if (value instanceof JsonString) {
- dep.addEvidence(type, PACKAGE_JSON, key, ((JsonString) value).getString(), Confidence.HIGHEST);
- } else if (value instanceof JsonObject) {
- final JsonObject jsonObject = (JsonObject) value;
- for (final Map.Entry entry : jsonObject.entrySet()) {
- final String property = entry.getKey();
- final JsonValue subValue = entry.getValue();
- if (subValue instanceof JsonString) {
- dep.addEvidence(type, PACKAGE_JSON,
- String.format("%s.%s", key, property),
- ((JsonString) subValue).getString(),
- Confidence.HIGHEST);
- } else {
- LOGGER.warn("JSON sub-value not string as expected: {}", subValue);
- }
- }
- } else {
- LOGGER.warn("JSON value not string or JSON object as expected: {}", value);
- }
- }
- }
}
diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/SwiftPackageManagerAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/SwiftPackageManagerAnalyzer.java
index 90a3cddee..fda55a799 100644
--- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/SwiftPackageManagerAnalyzer.java
+++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/SwiftPackageManagerAnalyzer.java
@@ -67,7 +67,7 @@ public class SwiftPackageManagerAnalyzer extends AbstractFileTypeAnalyzer {
public static final String SPM_FILE_NAME = "Package.swift";
/**
- * Filter that detects files named "package.json".
+ * Filter that detects files named "Package.swift".
*/
private static final FileFilter SPM_FILE_FILTER = FileFilterBuilder.newInstance().addFilenames(SPM_FILE_NAME).build();
diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/nsp/NspSearch.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/nsp/NspSearch.java
index 327952a25..eb0815977 100644
--- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/nsp/NspSearch.java
+++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/nsp/NspSearch.java
@@ -123,6 +123,7 @@ public class NspSearch {
try (InputStream in = new BufferedInputStream(conn.getInputStream());
JsonReader jsonReader = Json.createReader(in)) {
final JsonArray array = jsonReader.readArray();
+
if (array != null) {
for (int i = 0; i < array.size(); i++) {
final JsonObject object = array.getJsonObject(i);
diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/reporting/EscapeTool.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/reporting/EscapeTool.java
index f2e544be0..7911b47e6 100644
--- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/reporting/EscapeTool.java
+++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/reporting/EscapeTool.java
@@ -123,9 +123,13 @@ public class EscapeTool {
*/
public String csv(String text) {
if (text == null || text.isEmpty()) {
- return text;
+ return "\"\"";
}
- return StringEscapeUtils.escapeCsv(text.trim().replace("\n", " "));
+ final String str = text.trim().replace("\n", " ");
+ if (str.length() == 0) {
+ return "\"\"";
+ }
+ return StringEscapeUtils.escapeCsv(str);
}
/**
@@ -137,7 +141,7 @@ public class EscapeTool {
*/
public String csvIdentifiers(Set ids) {
if (ids == null || ids.isEmpty()) {
- return "";
+ return "\"\"";
}
boolean addComma = false;
final StringBuilder sb = new StringBuilder();
@@ -151,6 +155,9 @@ public class EscapeTool {
sb.append(id.getValue());
}
}
+ if (sb.length() == 0) {
+ return "\"\"";
+ }
return StringEscapeUtils.escapeCsv(sb.toString());
}
@@ -163,7 +170,7 @@ public class EscapeTool {
*/
public String csvCpe(Set ids) {
if (ids == null || ids.isEmpty()) {
- return "";
+ return "\"\"";
}
boolean addComma = false;
final StringBuilder sb = new StringBuilder();
@@ -177,6 +184,9 @@ public class EscapeTool {
sb.append(id.getValue());
}
}
+ if (sb.length() == 0) {
+ return "\"\"";
+ }
return StringEscapeUtils.escapeCsv(sb.toString());
}
@@ -189,7 +199,7 @@ public class EscapeTool {
*/
public String csvCpeConfidence(Set ids) {
if (ids == null || ids.isEmpty()) {
- return "";
+ return "\"\"";
}
boolean addComma = false;
final StringBuilder sb = new StringBuilder();
@@ -203,6 +213,9 @@ public class EscapeTool {
sb.append(id.getConfidence());
}
}
+ if (sb.length() == 0) {
+ return "\"\"";
+ }
return StringEscapeUtils.escapeCsv(sb.toString());
}
@@ -215,12 +228,12 @@ public class EscapeTool {
*/
public String csvGav(Set ids) {
if (ids == null || ids.isEmpty()) {
- return "";
+ return "\"\"";
}
boolean addComma = false;
final StringBuilder sb = new StringBuilder();
for (Identifier id : ids) {
- if ("maven".equals(id.getType())) {
+ if ("maven".equals(id.getType()) || "npm".equals(id.getType())) {
if (addComma) {
sb.append(", ");
} else {
@@ -229,7 +242,9 @@ public class EscapeTool {
sb.append(id.getValue());
}
}
+ if (sb.length() == 0) {
+ return "\"\"";
+ }
return StringEscapeUtils.escapeCsv(sb.toString());
}
-
}
diff --git a/dependency-check-core/src/main/resources/dependencycheck.properties b/dependency-check-core/src/main/resources/dependencycheck.properties
index 0a6053d36..259c724d4 100644
--- a/dependency-check-core/src/main/resources/dependencycheck.properties
+++ b/dependency-check-core/src/main/resources/dependencycheck.properties
@@ -126,4 +126,6 @@ analyzer.nvdcve.enabled=true
analyzer.vulnerabilitysuppression.enabled=true
updater.nvdcve.enabled=true
updater.versioncheck.enabled=true
-analyzer.versionfilter.enabled=true
\ No newline at end of file
+analyzer.versionfilter.enabled=true
+
+ecosystem.skip.cpeanalyzer=npm
\ No newline at end of file
diff --git a/dependency-check-core/src/main/resources/templates/csvReport.vsl b/dependency-check-core/src/main/resources/templates/csvReport.vsl
index 816384cee..18d71503c 100644
--- a/dependency-check-core/src/main/resources/templates/csvReport.vsl
+++ b/dependency-check-core/src/main/resources/templates/csvReport.vsl
@@ -17,7 +17,7 @@ Copyright (c) 2017 Jeremy Long. All Rights Reserved.
@author Jeremy Long
@version 1 *###
-"Project","ScanDate","DependencyName","DependencyPath","Description","License","Md5","Sha1","Identifiers","CPE","CVE","CWE","Vulnerability","Source","Severity","CVSSv2","GAV","CPE Confidence","Evidence Count"
+"Project","ScanDate","DependencyName","DependencyPath","Description","License","Md5","Sha1","Identifiers","CPE","CVE","CWE","Vulnerability","Source","Severity","CVSSv2","Build Coordinates","CPE Confidence","Evidence Count"
#macro(writeSev $score)#if($score<4.0)"Low"#elseif($score>=7.0)"High"#else"Medium"#end#end
#foreach($dependency in $dependencies)#if($dependency.getVulnerabilities().size()>0)
#foreach($vuln in $dependency.getVulnerabilities(true))
diff --git a/dependency-check-core/src/main/resources/templates/htmlReport.vsl b/dependency-check-core/src/main/resources/templates/htmlReport.vsl
index 08170c044..5d3ec38b6 100644
--- a/dependency-check-core/src/main/resources/templates/htmlReport.vsl
+++ b/dependency-check-core/src/main/resources/templates/htmlReport.vsl
@@ -623,7 +623,7 @@ Getting Help:
| Dependency |
CPE |
- GAV |
+ Coordinates |
Highest Severity |
CVE Count |
CPE Confidence |
@@ -638,7 +638,7 @@ Getting Help:
#end
#if ($id.type=="npm")
- $enc.html($id.value): $enc.html($id.description)
+ $enc.html($id.value)
#end
#end
diff --git a/dependency-check-core/src/main/resources/templates/jsonReport.vsl b/dependency-check-core/src/main/resources/templates/jsonReport.vsl
index decfeaa83..dada8239e 100644
--- a/dependency-check-core/src/main/resources/templates/jsonReport.vsl
+++ b/dependency-check-core/src/main/resources/templates/jsonReport.vsl
@@ -41,22 +41,15 @@
"identifiers": [
#set($loopCount=0)
#foreach($id in $related.getIdentifiers())
- #if ($id.type=="maven")
+ #if ($id.type=="maven" || $id.type=="npm")
#set($loopCount=$loopCount+1)
#if($loopCount>1),#end
{
"type": "$enc.json($id.type)",
- "name": "$id.value"
+ "id": "$id.value"
#if ($id.url),"url": "$enc.json($id.url)"#end
#if ($id.notes),"notes": "$enc.json($id.notes)"#end
- }
- #end
- #if ($id.type=="npm")
- #set($loopCount=$loopCount+1)
- #if($loopCount>1),#end
- {
- "id":"$enc.json($id.value)"
- ,"description":"$enc.json($id.description)"
+ #if ($id.description),"description":"$enc.json($id.description)"#end
}
#end
#end
diff --git a/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/AnalyzerServiceTest.java b/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/AnalyzerServiceTest.java
index 00063719a..efd825357 100644
--- a/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/AnalyzerServiceTest.java
+++ b/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/AnalyzerServiceTest.java
@@ -76,16 +76,12 @@ public class AnalyzerServiceTest extends BaseDBTestCase {
AnalyzerService instance = new AnalyzerService(Thread.currentThread().getContextClassLoader(), getSettings());
List result = instance.getAnalyzers();
String experimental = "CMake Analyzer";
- String retired = "Node.js Package Analyzer";
boolean found = false;
boolean retiredFound = false;
for (Analyzer a : result) {
if (experimental.equals(a.getName())) {
found = true;
}
- if (retired.equals(a.getName())) {
- retiredFound = true;
- }
}
assertFalse("Experimental analyzer loaded when set to false", found);
assertFalse("Retired analyzer loaded when set to false", retiredFound);
@@ -99,13 +95,10 @@ public class AnalyzerServiceTest extends BaseDBTestCase {
if (experimental.equals(a.getName())) {
found = true;
}
- if (retired.equals(a.getName())) {
- retiredFound = true;
- }
}
assertTrue("Experimental analyzer not loaded when set to true", found);
assertFalse("Retired analyzer loaded when set to false", retiredFound);
-
+
getSettings().setBoolean(Settings.KEYS.ANALYZER_EXPERIMENTAL_ENABLED, false);
getSettings().setBoolean(Settings.KEYS.ANALYZER_RETIRED_ENABLED, true);
instance = new AnalyzerService(Thread.currentThread().getContextClassLoader(), getSettings());
@@ -116,11 +109,8 @@ public class AnalyzerServiceTest extends BaseDBTestCase {
if (experimental.equals(a.getName())) {
found = true;
}
- if (retired.equals(a.getName())) {
- retiredFound = true;
- }
}
assertFalse("Experimental analyzer loaded when set to false", found);
- assertTrue("Retired analyzer not loaded when set to true", retiredFound);
+ //assertTrue("Retired analyzer not loaded when set to true", retiredFound);
}
}
diff --git a/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/NodePackageAnalyzerTest.java b/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/NodePackageAnalyzerTest.java
index b243ea3cd..b8ecf1a5f 100644
--- a/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/NodePackageAnalyzerTest.java
+++ b/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/NodePackageAnalyzerTest.java
@@ -29,6 +29,7 @@ import java.io.File;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.*;
+import org.owasp.dependencycheck.Engine;
import org.owasp.dependencycheck.dependency.EvidenceType;
/**
@@ -42,6 +43,10 @@ public class NodePackageAnalyzerTest extends BaseTest {
* The analyzer to test.
*/
private NodePackageAnalyzer analyzer;
+ /**
+ * A reference to the engine.
+ */
+ private Engine engine;
/**
* Correctly setup the analyzer for testing.
@@ -52,14 +57,15 @@ public class NodePackageAnalyzerTest extends BaseTest {
@Override
public void setUp() throws Exception {
super.setUp();
+ engine = new Engine(this.getSettings());
analyzer = new NodePackageAnalyzer();
analyzer.setFilesMatched(true);
analyzer.initialize(getSettings());
- analyzer.prepare(null);
+ analyzer.prepare(engine);
}
/**
- * Cleanup the analyzer's temp files, etc.
+ * Cleanup temp files, close resources, etc.
*
* @throws Exception thrown if there is a problem
*/
@@ -67,6 +73,7 @@ public class NodePackageAnalyzerTest extends BaseTest {
@Override
public void tearDown() throws Exception {
analyzer.close();
+ engine.close();
super.tearDown();
}
@@ -83,7 +90,8 @@ public class NodePackageAnalyzerTest extends BaseTest {
*/
@Test
public void testSupportsFiles() {
- assertThat(analyzer.accept(new File("package.json")), is(true));
+ assertThat(analyzer.accept(new File("package-lock.json")), is(true));
+ assertThat(analyzer.accept(new File("npm-shrinkwrap.json")), is(true));
}
/**
@@ -92,17 +100,39 @@ public class NodePackageAnalyzerTest extends BaseTest {
* @throws AnalysisException is thrown when an exception occurs.
*/
@Test
- public void testAnalyzePackageJson() throws AnalysisException {
- final Dependency result = new Dependency(BaseTest.getResourceAsFile(this,
- "nodejs/node_modules/dns-sync/package.json"));
- analyzer.analyze(result, null);
+ public void testAnalyzeShrinkwrapJson() throws AnalysisException {
+ final Dependency toScan = new Dependency(BaseTest.getResourceAsFile(this,
+ "nodejs/npm-shrinkwrap.json"));
+ analyzer.analyze(toScan, engine);
+ assertEquals("Expected 1 dependency", engine.getDependencies().length, 1);
+ final Dependency result = engine.getDependencies()[0];
final String vendorString = result.getEvidence(EvidenceType.VENDOR).toString();
assertThat(vendorString, containsString("Sanjeev Koranga"));
- assertThat(vendorString, containsString("dns-sync_project"));
+ assertThat(vendorString, containsString("dns-sync"));
assertThat(result.getEvidence(EvidenceType.PRODUCT).toString(), containsString("dns-sync"));
assertThat(result.getEvidence(EvidenceType.VERSION).toString(), containsString("0.1.0"));
assertEquals(NodePackageAnalyzer.DEPENDENCY_ECOSYSTEM, result.getEcosystem());
assertEquals("dns-sync", result.getName());
assertEquals("0.1.0", result.getVersion());
}
+
+ /**
+ * Test of inspect method, of class PythonDistributionAnalyzer.
+ *
+ * @throws AnalysisException is thrown when an exception occurs.
+ */
+ @Test
+ public void testAnalyzePackageJsonWithShrinkwrap() throws AnalysisException {
+ final Dependency packageLock = new Dependency(BaseTest.getResourceAsFile(this,
+ "nodejs/package-lock.json"));
+ final Dependency shrinkwrap = new Dependency(BaseTest.getResourceAsFile(this,
+ "nodejs/npm-shrinkwrap.json"));
+ engine.addDependency(packageLock);
+ engine.addDependency(shrinkwrap);
+ assertEquals(2, engine.getDependencies().length);
+ analyzer.analyze(packageLock, engine);
+ assertEquals(1, engine.getDependencies().length); //package-lock was removed without analysis
+ analyzer.analyze(shrinkwrap, engine);
+ assertEquals(1, engine.getDependencies().length); //shrinkwrap was removed with analysis adding 1 dependency
+ }
}
diff --git a/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/NspAnalyzerTest.java b/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/NspAnalyzerTest.java
index dfcd98d3f..9cc3c7ea6 100644
--- a/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/NspAnalyzerTest.java
+++ b/dependency-check-core/src/test/java/org/owasp/dependencycheck/analyzer/NspAnalyzerTest.java
@@ -1,7 +1,5 @@
package org.owasp.dependencycheck.analyzer;
-import org.junit.After;
-import org.junit.Before;
import org.junit.Test;
import org.owasp.dependencycheck.BaseTest;
import org.owasp.dependencycheck.analyzer.exception.AnalysisException;
@@ -11,91 +9,91 @@ import java.io.File;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.*;
+import org.owasp.dependencycheck.Engine;
import org.owasp.dependencycheck.dependency.EvidenceType;
+import org.owasp.dependencycheck.exception.InitializationException;
public class NspAnalyzerTest extends BaseTest {
- private NspAnalyzer analyzer;
-
- @Before
- @Override
- public void setUp() throws Exception {
- super.setUp();
- analyzer = new NspAnalyzer();
- analyzer.setFilesMatched(true);
- analyzer.initialize(getSettings());
- analyzer.prepare(null);
- }
-
- @After
- @Override
- public void tearDown() throws Exception {
- analyzer.close();
- super.tearDown();
- }
-
@Test
public void testGetName() {
+ NspAnalyzer analyzer = new NspAnalyzer();
assertThat(analyzer.getName(), is("Node Security Platform Analyzer"));
}
@Test
public void testSupportsFiles() {
+ NspAnalyzer analyzer = new NspAnalyzer();
assertThat(analyzer.accept(new File("package.json")), is(true));
}
@Test
- public void testAnalyzePackage() throws AnalysisException {
- final Dependency result = new Dependency(BaseTest.getResourceAsFile(this, "nsp/package.json"));
- analyzer.analyze(result, null);
-
- assertTrue(result.getEvidence(EvidenceType.VENDOR).toString().contains("owasp-nodejs-goat_project"));
- assertTrue(result.getEvidence(EvidenceType.PRODUCT).toString().contains("A tool to learn OWASP Top 10 for node.js developers"));
- assertTrue(result.getEvidence(EvidenceType.VERSION).toString().contains("1.3.0"));
+ public void testAnalyzePackage() throws AnalysisException, InitializationException {
+ try (Engine engine = new Engine(getSettings())) {
+ NspAnalyzer analyzer = new NspAnalyzer();
+ analyzer.setFilesMatched(true);
+ analyzer.initialize(getSettings());
+ analyzer.prepare(engine);
+ final Dependency toScan = new Dependency(BaseTest.getResourceAsFile(this, "nsp/package.json"));
+ analyzer.analyze(toScan, engine);
+ boolean found = false;
+ assertEquals("4 dependencies should be identified", 4, engine.getDependencies().length);
+ for (Dependency result : engine.getDependencies()) {
+ if ("package.json?uglify-js".equals(result.getFileName())) {
+ found = true;
+ assertTrue(result.getEvidence(EvidenceType.VENDOR).toString().contains("uglify-js"));
+ assertTrue(result.getEvidence(EvidenceType.PRODUCT).toString().contains("uglify-js"));
+ assertTrue(result.getEvidence(EvidenceType.VERSION).toString().contains("2.4.24"));
+ assertTrue(result.isVirtual());
+ }
+ }
+ assertTrue("Uglify was not found", found);
+ }
}
@Test
- public void testAnalyzeEmpty() throws AnalysisException {
- final Dependency result = new Dependency(BaseTest.getResourceAsFile(this, "nsp/empty.json"));
- analyzer.analyze(result, null);
+ public void testAnalyzeEmpty() throws AnalysisException, InitializationException {
+ try (Engine engine = new Engine(getSettings())) {
+ NspAnalyzer analyzer = new NspAnalyzer();
+ analyzer.setFilesMatched(true);
+ analyzer.initialize(getSettings());
+ analyzer.prepare(engine);
+ final Dependency result = new Dependency(BaseTest.getResourceAsFile(this, "nsp/empty.json"));
+ analyzer.analyze(result, engine);
- assertEquals(result.getEvidence(EvidenceType.VENDOR).size(), 0);
- assertEquals(result.getEvidence(EvidenceType.PRODUCT).size(), 0);
- assertEquals(result.getEvidence(EvidenceType.VERSION).size(), 0);
+ assertEquals(result.getEvidence(EvidenceType.VENDOR).size(), 0);
+ assertEquals(result.getEvidence(EvidenceType.PRODUCT).size(), 0);
+ assertEquals(result.getEvidence(EvidenceType.VERSION).size(), 0);
+ }
}
@Test
- public void testAnalyzePackageJsonWithBundledDeps() throws AnalysisException {
- final Dependency result = new Dependency(BaseTest.getResourceAsFile(this, "nsp/bundled.deps.package.json"));
- analyzer.analyze(result, null);
-
- assertTrue(result.getEvidence(EvidenceType.VENDOR).toString().contains("Philipp Dunkel "));
- assertTrue(result.getEvidence(EvidenceType.PRODUCT).toString().contains("Native Access to Mac OS-X FSEvents"));
- assertTrue(result.getEvidence(EvidenceType.VERSION).toString().contains("1.1.1"));
+ public void testAnalyzePackageJsonInNodeModulesDirectory() throws AnalysisException, InitializationException {
+ try (Engine engine = new Engine(getSettings())) {
+ NspAnalyzer analyzer = new NspAnalyzer();
+ analyzer.setFilesMatched(true);
+ analyzer.initialize(getSettings());
+ analyzer.prepare(engine);
+ final Dependency toScan = new Dependency(BaseTest.getResourceAsFile(this, "nodejs/node_modules/dns-sync/package.json"));
+ engine.addDependency(toScan);
+ analyzer.analyze(toScan, engine);
+ assertEquals("No dependencies should exist", 0, engine.getDependencies().length);
+ }
}
@Test
- public void testAnalyzePackageJsonWithLicenseObject() throws AnalysisException {
- final Dependency result = new Dependency(BaseTest.getResourceAsFile(this, "nsp/license.obj.package.json"));
- analyzer.analyze(result, null);
-
- assertTrue(result.getEvidence(EvidenceType.VENDOR).toString().contains("Twitter, Inc."));
- assertTrue(result.getEvidence(EvidenceType.PRODUCT).toString().contains("The most popular front-end framework for developing responsive, mobile first projects on the web"));
- assertTrue(result.getEvidence(EvidenceType.VERSION).toString().contains("3.2.0"));
- }
-
- @Test
- public void testAnalyzePackageJsonInNodeModulesDirectory() throws AnalysisException {
- final Dependency result = new Dependency(BaseTest.getResourceAsFile(this, "nodejs/node_modules/dns-sync/package.json"));
- analyzer.analyze(result, null);
- // node modules are not scanned - no evidence is collected
- assertTrue(result.size() == 0);
- }
-
- @Test
- public void testAnalyzeInvalidPackageMissingName() throws AnalysisException {
- final Dependency result = new Dependency(BaseTest.getResourceAsFile(this, "nsp/minimal-invalid.json"));
- analyzer.analyze(result, null);
- // Upon analysis, not throwing an exception in this case, is all that's required to pass this test
+ public void testAnalyzeInvalidPackageMissingName() throws AnalysisException, InitializationException {
+ try (Engine engine = new Engine(getSettings())) {
+ NspAnalyzer analyzer = new NspAnalyzer();
+ analyzer.setFilesMatched(true);
+ analyzer.initialize(getSettings());
+ analyzer.prepare(engine);
+ final Dependency result = new Dependency(BaseTest.getResourceAsFile(this, "nsp/minimal-invalid.json"));
+ analyzer.analyze(result, engine);
+ // Upon analysis, not throwing an exception in this case, is all that's required to pass this test
+ } catch(Throwable ex) {
+ fail("This test should not throw an exception");
+ throw ex;
+ }
}
}
diff --git a/dependency-check-core/src/test/java/org/owasp/dependencycheck/reporting/EscapeToolTest.java b/dependency-check-core/src/test/java/org/owasp/dependencycheck/reporting/EscapeToolTest.java
index 6890d2469..a94180c11 100644
--- a/dependency-check-core/src/test/java/org/owasp/dependencycheck/reporting/EscapeToolTest.java
+++ b/dependency-check-core/src/test/java/org/owasp/dependencycheck/reporting/EscapeToolTest.java
@@ -126,12 +126,12 @@ public class EscapeToolTest {
public void testCsv() {
String text = null;
EscapeTool instance = new EscapeTool();
- String expResult = null;
+ String expResult = "\"\"";
String result = instance.csv(text);
assertEquals(expResult, result);
text = "";
- expResult = "";
+ expResult = "\"\"";
result = instance.csv(text);
assertEquals(expResult, result);
@@ -148,38 +148,38 @@ public class EscapeToolTest {
public void testCsvIdentifiers() {
EscapeTool instance = new EscapeTool();
Set ids = null;
- String expResult = "";
+ String expResult = "\"\"";
String result = instance.csvIdentifiers(ids);
assertEquals(expResult, result);
ids = new HashSet<>();
- expResult = "";
+ expResult = "\"\"";
result = instance.csvIdentifiers(ids);
assertEquals(expResult, result);
ids = new HashSet<>();
ids.add(new Identifier("cpe", "cpe:/a:somegroup:something:1.0", ""));
- expResult = "";
+ expResult = "\"\"";
result = instance.csvIdentifiers(ids);
assertEquals(expResult, result);
ids = new HashSet<>();
- ids.add(new Identifier("gav", "somegroup:something:1.0", ""));
+ ids.add(new Identifier("maven", "somegroup:something:1.0", ""));
expResult = "somegroup:something:1.0";
result = instance.csvIdentifiers(ids);
assertEquals(expResult, result);
ids = new HashSet<>();
ids.add(new Identifier("cpe", "cpe:/a:somegroup:something:1.0", ""));
- ids.add(new Identifier("gav", "somegroup:something:1.0", ""));
+ ids.add(new Identifier("maven", "somegroup:something:1.0", ""));
expResult = "somegroup:something:1.0";
result = instance.csvIdentifiers(ids);
assertEquals(expResult, result);
ids = new HashSet<>();
ids.add(new Identifier("cpe", "cpe:/a:somegroup:something:1.0", ""));
- ids.add(new Identifier("gav", "somegroup:something:1.0", ""));
- ids.add(new Identifier("gav", "somegroup2:something:1.2", ""));
+ ids.add(new Identifier("maven", "somegroup:something:1.0", ""));
+ ids.add(new Identifier("maven", "somegroup2:something:1.2", ""));
expResult = "\"somegroup:something:1.0, somegroup2:something:1.2\"";
String expResult2 = "\"somegroup2:something:1.2, somegroup:something:1.0\"";
result = instance.csvIdentifiers(ids);
@@ -193,18 +193,18 @@ public class EscapeToolTest {
public void testCsvCpe() {
EscapeTool instance = new EscapeTool();
Set ids = null;
- String expResult = "";
+ String expResult = "\"\"";
String result = instance.csvCpe(ids);
assertEquals(expResult, result);
ids = new HashSet<>();
- expResult = "";
+ expResult = "\"\"";
result = instance.csvCpe(ids);
assertEquals(expResult, result);
ids = new HashSet<>();
- ids.add(new Identifier("gav", "somegroup:something:1.0", ""));
- expResult = "";
+ ids.add(new Identifier("maven", "somegroup:something:1.0", ""));
+ expResult = "\"\"";
result = instance.csvCpe(ids);
assertEquals(expResult, result);
@@ -216,14 +216,14 @@ public class EscapeToolTest {
ids = new HashSet<>();
ids.add(new Identifier("cpe", "cpe:/a:somegroup:something:1.0", ""));
- ids.add(new Identifier("gav", "somegroup:something:1.0", ""));
+ ids.add(new Identifier("maven", "somegroup:something:1.0", ""));
expResult = "cpe:/a:somegroup:something:1.0";
result = instance.csvCpe(ids);
assertEquals(expResult, result);
ids = new HashSet<>();
ids.add(new Identifier("cpe", "cpe:/a:somegroup:something:1.0", ""));
- ids.add(new Identifier("gav", "somegroup:something:1.0", ""));
+ ids.add(new Identifier("maven", "somegroup:something:1.0", ""));
ids.add(new Identifier("cpe", "cpe:/a:somegroup2:something:1.2", ""));
expResult = "\"cpe:/a:somegroup:something:1.0, cpe:/a:somegroup2:something:1.2\"";
String expResult2 = "\"cpe:/a:somegroup2:something:1.2, cpe:/a:somegroup:something:1.0\"";
@@ -238,18 +238,18 @@ public class EscapeToolTest {
public void testCsvCpeConfidence() {
EscapeTool instance = new EscapeTool();
Set ids = null;
- String expResult = "";
+ String expResult = "\"\"";
String result = instance.csvCpeConfidence(ids);
assertEquals(expResult, result);
ids = new HashSet<>();
- expResult = "";
+ expResult = "\"\"";
result = instance.csvCpeConfidence(ids);
assertEquals(expResult, result);
ids = new HashSet<>();
- ids.add(new Identifier("gav", "somegroup:something:1.0", ""));
- expResult = "";
+ ids.add(new Identifier("maven", "somegroup:something:1.0", ""));
+ expResult = "\"\"";
result = instance.csvCpeConfidence(ids);
assertEquals(expResult, result);
@@ -268,7 +268,7 @@ public class EscapeToolTest {
Identifier i2 = new Identifier("cpe", "cpe:/a:somegroup:something2:1.0", "");
i2.setConfidence(Confidence.MEDIUM);
ids.add(i2);
- Identifier i3 = new Identifier("gav", "somegroup:something:1.0", "");
+ Identifier i3 = new Identifier("maven", "somegroup:something:1.0", "");
i3.setConfidence(Confidence.LOW);
ids.add(i3);
@@ -285,18 +285,18 @@ public class EscapeToolTest {
public void testCsvGav() {
EscapeTool instance = new EscapeTool();
Set ids = null;
- String expResult = "";
+ String expResult = "\"\"";
String result = instance.csvGav(ids);
assertEquals(expResult, result);
ids = new HashSet<>();
- expResult = "";
+ expResult = "\"\"";
result = instance.csvGav(ids);
assertEquals(expResult, result);
ids = new HashSet<>();
ids.add(new Identifier("cpe", "somegroup:something:1.0", ""));
- expResult = "";
+ expResult = "\"\"";
result = instance.csvGav(ids);
assertEquals(expResult, result);
diff --git a/dependency-check-core/src/test/resources/dependencycheck.properties b/dependency-check-core/src/test/resources/dependencycheck.properties
index 94a2cdd25..ce2875943 100644
--- a/dependency-check-core/src/test/resources/dependencycheck.properties
+++ b/dependency-check-core/src/test/resources/dependencycheck.properties
@@ -123,3 +123,5 @@ analyzer.nvdcve.enabled=true
analyzer.vulnerabilitysuppression.enabled=true
updater.nvdcve.enabled=true
updater.versioncheck.enabled=true
+
+ecosystem.skip.cpeanalyzer=npm
\ No newline at end of file
diff --git a/dependency-check-core/src/test/resources/nodejs/npm-shrinkwrap.json b/dependency-check-core/src/test/resources/nodejs/npm-shrinkwrap.json
new file mode 100644
index 000000000..32f7033f5
--- /dev/null
+++ b/dependency-check-core/src/test/resources/nodejs/npm-shrinkwrap.json
@@ -0,0 +1,18 @@
+{
+ "name": "test",
+ "version": "0.0.1",
+ "lockfileVersion": 1,
+ "requires": true,
+ "dependencies": {
+ "dns-sync": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/dns-sync/-/dns-sync-0.1.0.tgz",
+ "integrity": "sha1-gPcpFC513UtfSx0+Upcx7jEplHI=",
+ "requires": {
+ "debug": "2.6.9",
+ "shelljs": "0.5.3"
+ }
+ }
+ }
+ }
+
\ No newline at end of file
diff --git a/dependency-check-core/src/test/resources/nodejs/package-lock.json b/dependency-check-core/src/test/resources/nodejs/package-lock.json
new file mode 100644
index 000000000..16d7aca04
--- /dev/null
+++ b/dependency-check-core/src/test/resources/nodejs/package-lock.json
@@ -0,0 +1,17 @@
+{
+ "name": "test",
+ "version": "0.0.1",
+ "lockfileVersion": 1,
+ "requires": true,
+ "dependencies": {
+ "dns-sync": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/dns-sync/-/dns-sync-0.1.0.tgz",
+ "integrity": "sha1-gPcpFC513UtfSx0+Upcx7jEplHI=",
+ "requires": {
+ "debug": "2.6.9",
+ "shelljs": "0.5.3"
+ }
+ }
+ }
+}
diff --git a/dependency-check-core/src/test/resources/nsp/bundled.deps.package.json b/dependency-check-core/src/test/resources/nsp/bundled.deps.package.json
deleted file mode 100644
index 281347992..000000000
--- a/dependency-check-core/src/test/resources/nsp/bundled.deps.package.json
+++ /dev/null
@@ -1,48 +0,0 @@
-{
- "name": "fsevents",
- "version": "1.1.1",
- "description": "Native Access to Mac OS-X FSEvents",
- "main": "fsevents.js",
- "dependencies": {
- "nan": "^2.3.0",
- "node-pre-gyp": "^0.6.29"
- },
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=0.8.0"
- },
- "scripts": {
- "install": "node install",
- "prepublish": "if [ $(npm -v | head -c 1) -lt 3 ]; then exit 1; fi && npm dedupe",
- "test": "tap ./test"
- },
- "binary": {
- "module_name": "fse",
- "module_path": "./lib/binding/{configuration}/{node_abi}-{platform}-{arch}/",
- "remote_path": "./v{version}/",
- "package_name": "{module_name}-v{version}-{node_abi}-{platform}-{arch}.tar.gz",
- "host": "https://fsevents-binaries.s3-us-west-2.amazonaws.com"
- },
- "repository": {
- "type": "git",
- "url": "https://github.com/strongloop/fsevents.git"
- },
- "keywords": [
- "fsevents",
- "mac"
- ],
- "author": "Philipp Dunkel ",
- "license": "MIT",
- "bugs": {
- "url": "https://github.com/strongloop/fsevents/issues"
- },
- "bundledDependencies": [
- "node-pre-gyp"
- ],
- "homepage": "https://github.com/strongloop/fsevents",
- "devDependencies": {
- "tap": "~0.4.8"
- }
-}
diff --git a/dependency-check-core/src/test/resources/nsp/license.obj.package.json b/dependency-check-core/src/test/resources/nsp/license.obj.package.json
deleted file mode 100644
index 3243fa8ba..000000000
--- a/dependency-check-core/src/test/resources/nsp/license.obj.package.json
+++ /dev/null
@@ -1,81 +0,0 @@
-{
- "name": "bootstrap",
- "description": "The most popular front-end framework for developing responsive, mobile first projects on the web.",
- "version": "3.2.0",
- "keywords": [
- "css",
- "less",
- "mobile-first",
- "responsive",
- "front-end",
- "framework",
- "web"
- ],
- "homepage": "http://getbootstrap.com",
- "author": "Twitter, Inc.",
- "scripts": {
- "test": "grunt test"
- },
- "style": "dist/css/bootstrap.css",
- "less": "less/bootstrap.less",
- "repository": {
- "type": "git",
- "url": "https://github.com/twbs/bootstrap.git"
- },
- "bugs": {
- "url": "https://github.com/twbs/bootstrap/issues"
- },
- "license": {
- "type": "MIT",
- "url": "https://github.com/twbs/bootstrap/blob/master/LICENSE"
- },
- "devDependencies": {
- "btoa": "~1.1.2",
- "glob": "~4.0.2",
- "grunt": "~0.4.5",
- "grunt-autoprefixer": "~0.7.6",
- "grunt-banner": "~0.2.3",
- "grunt-contrib-clean": "~0.5.0",
- "grunt-contrib-concat": "~0.4.0",
- "grunt-contrib-connect": "~0.8.0",
- "grunt-contrib-copy": "~0.5.0",
- "grunt-contrib-csslint": "~0.2.0",
- "grunt-contrib-cssmin": "~0.10.0",
- "grunt-contrib-jade": "~0.12.0",
- "grunt-contrib-jshint": "~0.10.0",
- "grunt-contrib-less": "~0.11.3",
- "grunt-contrib-qunit": "~0.5.1",
- "grunt-contrib-uglify": "~0.5.0",
- "grunt-contrib-watch": "~0.6.1",
- "grunt-csscomb": "~2.0.1",
- "grunt-exec": "~0.4.5",
- "grunt-html-validation": "~0.1.18",
- "grunt-jekyll": "~0.4.2",
- "grunt-jscs-checker": "~0.6.0",
- "grunt-saucelabs": "~8.1.0",
- "grunt-sed": "~0.1.1",
- "load-grunt-tasks": "~0.6.0",
- "markdown": "~0.5.0",
- "npm-shrinkwrap": "~3.1.6",
- "time-grunt": "~0.3.2"
- },
- "engines": {
- "node": "~0.10.1"
- },
- "jspm": {
- "main": "js/bootstrap",
- "directories": {
- "example": "examples",
- "lib": "dist"
- },
- "shim": {
- "js/bootstrap": {
- "imports": "jquery",
- "exports": "$"
- }
- },
- "buildConfig": {
- "uglify": true
- }
- }
-}
diff --git a/dependency-check-maven/pom.xml b/dependency-check-maven/pom.xml
index 0a238dcbb..6b8a3024c 100644
--- a/dependency-check-maven/pom.xml
+++ b/dependency-check-maven/pom.xml
@@ -20,7 +20,7 @@ Copyright (c) 2013 Jeremy Long. All Rights Reserved.
org.owasp
dependency-check-parent
- 3.0.3-SNAPSHOT
+ 3.1.0-SNAPSHOT
dependency-check-maven
maven-plugin
diff --git a/dependency-check-plugin/pom.xml b/dependency-check-plugin/pom.xml
index 828348391..0edee5725 100644
--- a/dependency-check-plugin/pom.xml
+++ b/dependency-check-plugin/pom.xml
@@ -21,7 +21,7 @@ Copyright (c) 2017 Jeremy Long. All Rights Reserved.
org.owasp
dependency-check-parent
- 3.0.3-SNAPSHOT
+ 3.1.0-SNAPSHOT
org.owasp
dependency-check-plugin
diff --git a/dependency-check-utils/pom.xml b/dependency-check-utils/pom.xml
index 15ed50461..ce4225b80 100644
--- a/dependency-check-utils/pom.xml
+++ b/dependency-check-utils/pom.xml
@@ -20,7 +20,7 @@ Copyright (c) 2014 - Jeremy Long. All Rights Reserved.
org.owasp
dependency-check-parent
- 3.0.3-SNAPSHOT
+ 3.1.0-SNAPSHOT
dependency-check-utils
diff --git a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/Settings.java b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/Settings.java
index dff450027..a33ba86cd 100644
--- a/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/Settings.java
+++ b/dependency-check-utils/src/main/java/org/owasp/dependencycheck/utils/Settings.java
@@ -442,6 +442,10 @@ public final class Settings {
* new version available.
*/
public static final String UPDATE_VERSION_CHECK_ENABLED = "updater.versioncheck.enabled";
+ /**
+ * The key to determine which ecosystems should skip the CPE analysis.
+ */
+ public static final String ECOSYSTEM_SKIP_CPEANALYZER = "ecosystem.skip.cpeanalyzer";
/**
* private constructor because this is a "utility" class containing
diff --git a/pom.xml b/pom.xml
index 86da3e0be..ee1b189e0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -20,7 +20,7 @@ Copyright (c) 2012 - Jeremy Long
org.owasp
dependency-check-parent
- 3.0.3-SNAPSHOT
+ 3.1.0-SNAPSHOT
pom
@@ -625,6 +625,11 @@ Copyright (c) 2012 - Jeremy Long
+
+ com.vdurmont
+ semver4j
+ 2.1.0
+
joda-time