├── .gitignore ├── src ├── log4j2.xml ├── leiningen │ └── dependency_check.clj └── lein_dependency_check │ └── core.clj ├── project.clj ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | .hgignore 11 | .hg/ 12 | .DS_Store 13 | .idea/ 14 | *.iml 15 | .lsp 16 | .clj-kondo 17 | -------------------------------------------------------------------------------- /src/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (def dependency-check-version "7.4.4") 2 | 3 | (defproject com.livingsocial/lein-dependency-check "1.4.1" 4 | :description "Clojure command line tool for detecting vulnerable project dependencies" 5 | :url "https://github.com/livingsocial/lein-dependency-check" 6 | :license {:name "The MIT License (MIT)" 7 | :url "https://opensource.org/licenses/MIT"} 8 | :dependencies [[org.clojure/clojure "1.11.1"] 9 | [org.owasp/dependency-check-core ~dependency-check-version] 10 | [org.owasp/dependency-check-utils ~dependency-check-version] 11 | [org.clojure/tools.cli "1.0.206"] 12 | [org.clojure/tools.logging "1.2.4"] 13 | [org.slf4j/jcl-over-slf4j "2.0.3"] 14 | [org.slf4j/slf4j-api "2.0.3"] 15 | [org.apache.logging.log4j/log4j-api "2.19.0"] 16 | [org.apache.logging.log4j/log4j-slf4j-impl "2.19.0"] 17 | [org.apache.logging.log4j/log4j-core "2.19.0"]] 18 | :eval-in-leiningen true) 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 LivingSocial 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /src/leiningen/dependency_check.clj: -------------------------------------------------------------------------------- 1 | (ns leiningen.dependency-check 2 | (:require [clojure.string :as string] 3 | [clojure.tools.cli :refer [parse-opts]] 4 | [leiningen.core.classpath :as cp] 5 | [leiningen.core.eval :as eval])) 6 | 7 | (def ^:private cli-options 8 | "Command line options accepts: 9 | log, throw, output-format, output-directory, properties-file." 10 | [[nil "--log" "Log to stdout"] 11 | [nil "--throw" "throw error when vulnerabilities found"] 12 | [nil "--min-cvss-v3 NUMBER" "minimum cvss score required to throw (use in conjunction with --throw)" 13 | :parse-fn #(Double/parseDouble %)] 14 | ["-p" "--properties-file FILE" "Specifies a file that contains properties to merge with defaults."] 15 | ["-f" "--output-format FORMAT(S)" "The output format to write to (XML, HTML, CSV, JSON, VULN, ALL). Default is HTML" 16 | :parse-fn (fn [output-format] 17 | (-> (string/replace output-format #":" "") 18 | (string/split #",")))] 19 | ["-o" "--output-directory DIR" "The folder to write to. The default is ./target"] 20 | ["-s" "--suppression-file FILE" "Path to the suppression XML file"]]) 21 | 22 | (def ^:private cli-defaults 23 | "Default options." 24 | {:output-format ["html"] 25 | :output-directory "target" 26 | :suppression-file "suppression.xml" 27 | :log false 28 | :throw false 29 | :min-cvss-v3 0}) 30 | 31 | (defn- dependency-check-project 32 | "Create a project to launch dependency-check, with only dependency-check as a dependency." 33 | [project] 34 | (if-let [dependency-check-vec (->> (:plugins project) 35 | (filter #(= 'com.livingsocial/lein-dependency-check 36 | (first %))) 37 | first)] 38 | {:dependencies [dependency-check-vec]} 39 | (throw (Exception. (str "dependency-check should be in your :plugins vector, " 40 | "either in your ~/.lein/profiles.clj or in " 41 | "the project itself."))))) 42 | 43 | (defn dependency-check 44 | "CLI options will overwrite config options. 45 | Default options: 46 | log => false 47 | throw => false 48 | output-format => :html 49 | output-directory => ./target" 50 | [project & args] 51 | (let [classpath (cp/get-classpath project) 52 | name (:name project) 53 | config (merge cli-defaults 54 | (:dependency-check project) 55 | (:options (parse-opts args cli-options)))] 56 | (eval/eval-in-project (dependency-check-project project) 57 | `(lein-dependency-check.core/main '~classpath '~name '~config) 58 | '(require 'lein-dependency-check.core)))) 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lein-dependency-check 2 | 3 | A Leiningen plugin for detecting vulnerable project dependencies. Basic clojure wrapper for [OWASP Dependency Check](https://www.owasp.org/index.php/OWASP_Dependency_Check). 4 | 5 | ## Configuration 6 | 7 | ### As a User-Level Plugin: 8 | 9 | To run dependency-check without having to add it to every Leiningen project as a project-level plugin, 10 | add dependency-check to the `:plugins` vector of your `:user` profile. E.g., a `~/.lein/profiles.clj` with dependency-check as a plugin - 11 | ``` 12 | {:user {:plugins [[com.livingsocial/lein-dependency-check "1.4.0"]]}} 13 | ``` 14 | 15 | If you are on Leiningen 1.x do `lein plugin install lein-dependency-check 1.4.0`. 16 | 17 | ### As a Project-Level Plugin: 18 | 19 | Add `[com.livingsocial/lein-dependency-check "1.4.0"]` to the `:plugins` vector of your project.clj. 20 | 21 | Project-level configuration may be provided under a `:dependency-check` key in your project.clj. Currently supported options are: 22 | * `log` log each vulnerability found to stdout 23 | * `throw` throw an exception after analysis and reporting if vulnerabilities are found, eg. to fail a build 24 | * `min-cvss-v3` minimum [CVSSv3](https://nvd.nist.gov/vuln-metrics/cvss) score required to `:throw` (number from 0.0 to 10.0), defaults to 0.0 25 | * `properties-file` Specifies a file that contains properties to merge with default values 26 | * `output-format` Vector of desired output formats: xml, csv, json, html, vuln, all 27 | * `output-directory` Directory to output results to 28 | * `suppression-file` Path to the suppression XML file 29 | 30 | ## Usage 31 | 32 | To generate a `dependency-check-report.html` report file to the current project's `target` directory, run: 33 | 34 | $ lein dependency-check 35 | 36 | To generate the report in XML format, run: 37 | 38 | $ lein dependency-check --output-format :xml 39 | 40 | To generate the report in multiple formats, run: 41 | 42 | $ lein dependency-check --output-format :xml,:json,:html,:csv 43 | 44 | To write the report to a different directory (e.g., `/tmp`), run: 45 | 46 | $ lein dependency-check --output-directory /tmp 47 | 48 | To set logging to stdout: 49 | 50 | $ lein dependency-check --log 51 | 52 | To set throw error when vulnerabilities found: 53 | 54 | $ lein dependency-check --throw 55 | 56 | To only throw if there are vulnerabilities with or above a given CVSSv3: 57 | 58 | $ lein dependency-check --throw --min-cvss-v3 5 59 | 60 | To set a suppression file: 61 | 62 | $ lein dependency-check --suppression-file /suppression.xml 63 | 64 | To set a properties file: 65 | 66 | $ lein dependency-check --properties-file /dependencycheck.properties 67 | 68 | ## Suppressing False Positives 69 | 70 | Support for suppressing false positives can be utilized by creating `suppression.xml` in your project's root directory. 71 | 72 | Suppression snippets can be copied from the HTML report file directly into `suppression.xml`. Upon rerun of `lein-dependency-check`, the suppression file will be used and warnings will not be present in report. 73 | 74 | For more information about dependency-check suppression system see https://jeremylong.github.io/DependencyCheck/general/suppression.html 75 | 76 | ## License 77 | 78 | Copyright © 2016 LivingSocial 79 | 80 | Distributed as open source under the terms of the [MIT 81 | License](http://opensource.org/licenses/MIT). 82 | -------------------------------------------------------------------------------- /src/lein_dependency_check/core.clj: -------------------------------------------------------------------------------- 1 | (ns lein-dependency-check.core 2 | (:require [clojure.java.io :as io] 3 | [clojure.pprint :refer [pprint]]) 4 | (:import (org.owasp.dependencycheck Engine) 5 | (org.owasp.dependencycheck.dependency Vulnerability) 6 | (org.owasp.dependencycheck.exception ExceptionCollection) 7 | (org.owasp.dependencycheck.utils Settings Settings$KEYS) 8 | (org.apache.logging.log4j.core.config Configurator) 9 | (java.io File))) 10 | 11 | (defonce SOURCE_DIR "src") 12 | (defonce LOG_CONF_FILE "log4j2.xml") 13 | 14 | (defn reconfigure-log4j 15 | "Reconfigures log4j from a log4j2.xml file" 16 | [] 17 | (let [^File config-file (io/file SOURCE_DIR LOG_CONF_FILE)] 18 | (when (.exists config-file) 19 | (prn "Reconfiguring log4j") 20 | (Configurator/initialize nil (.getPath config-file))))) 21 | 22 | (defn- target-files 23 | "Selects the files to be scanned" 24 | [project-classpath] 25 | (->> project-classpath 26 | (filter (partial re-find #"\.jar$")) 27 | (map io/file))) 28 | 29 | (defn- scan-files 30 | "Scans the specified files and returns the engine used to scan" 31 | [files {:keys [^File properties-file suppression-file]}] 32 | (let [settings (Settings.) 33 | _ (when (.exists (io/as-file suppression-file)) 34 | (.setString settings Settings$KEYS/SUPPRESSION_FILE suppression-file)) 35 | _ (when properties-file 36 | (.mergeProperties settings ^File (io/as-file properties-file))) 37 | engine (Engine. settings)] 38 | (prn "Scanning" (count files) "file(s)...") 39 | (doseq [^File file files] 40 | (prn "Scanning file" (.getCanonicalPath file)) 41 | (.scan engine file)) 42 | (prn "Done.") 43 | 44 | engine)) 45 | 46 | (defn- analyze-files 47 | "Analyzes the files scanned by the specified engine and returns the engine" 48 | [engine] 49 | (prn "Analyzing dependencies...") 50 | (.analyzeDependencies engine) 51 | (prn "Done.") 52 | 53 | engine) 54 | 55 | 56 | (defn- write-report 57 | [engine report-name output-format output-directory] 58 | (doseq [format output-format] 59 | (.writeReports engine report-name output-directory format (ExceptionCollection.))) 60 | engine) 61 | 62 | 63 | (defn- get-cvss-v3-score 64 | [^Vulnerability v] 65 | ;; Some vulnerabilities don't have CVSS assigned 66 | (if-let [cvss-v3 (.getCvssV3 v)] 67 | (.getBaseScore cvss-v3) 68 | 0)) 69 | 70 | 71 | (defn- handle-vulnerabilities [engine {:keys [log throw min-cvss-v3] :or {min-cvss-v3 0}}] 72 | (.close engine) 73 | (when-let [vulnerable-dependencies (->> (.getDependencies engine) 74 | (filter #((complement empty?) (.getVulnerabilities %))) 75 | (map (fn [dep] {:dependency dep 76 | :vulnerabilities (.getVulnerabilities dep)})) 77 | seq)] 78 | (when log 79 | (doall (map #(prn "Vulnerable Dependency:" (.toString %)) vulnerable-dependencies))) 80 | (when throw 81 | (let [max-score (->> vulnerable-dependencies 82 | (mapcat :vulnerabilities) 83 | (map get-cvss-v3-score) 84 | (apply max))] 85 | (when (>= max-score min-cvss-v3) 86 | (pprint vulnerable-dependencies) 87 | (throw (ex-info "Vulnerable Dependencies!" {:vulnerable (count vulnerable-dependencies)})))))) 88 | engine) 89 | 90 | 91 | (defn main 92 | "Scans the JAR files found on the class path and creates a vulnerability report." 93 | [project-classpath project-name config] 94 | (reconfigure-log4j) 95 | (let [{:keys [output-format output-directory]} config 96 | output-format (mapv name output-format) 97 | output-target (io/file output-directory)] 98 | (-> project-classpath 99 | target-files 100 | (scan-files config) 101 | analyze-files 102 | (write-report project-name output-format output-target) 103 | (handle-vulnerabilities config)))) 104 | --------------------------------------------------------------------------------