Skip to content

Commit

Permalink
Show short summary of findings
Browse files Browse the repository at this point in the history
Include a 2-line summary of findings that reports the number of
dependencies scanned, vulnerabilities found, and vulnerabilities
broken down by severity.

The break down by severity makes no effort to distinguish between CVSS2,
CVSS3 and CVSS4 scores. For example, CVSS2 has no Critical severity, so
a High CVSS2 could be classified as a Critical CVSS3/CVSS4. For a summary,
I think this is fine.

Accounts for possibility that data might have unspecified or
unrecognized severity values. I think this is less likely for
dependency-check (at least today as I've looked at the downloaded db),
but have less of an idea of what values github-advisory might return.

Some minor cleanups in touched code:
- de-duplicated shared scan logic in entrypoint ns
- moved logging setup to logging-config ns
- change kaocha test reporter to show tests being run

Closes #87
  • Loading branch information
lread committed Aug 26, 2024
1 parent 98b040a commit b17e1b9
Show file tree
Hide file tree
Showing 7 changed files with 87 additions and 55 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* Unreleased
* Fix: `--output json` now renders correctly & JSON output now pretty-printed [#116](https://github.com/clj-holmes/clj-watson/issues/116)
* Recognize CVSS2 and CVSS4 scores when available [#112](https://github.com/clj-holmes/clj-watson/issues/112)
* Show short summary of findings [#87](https://github.com/clj-holmes/clj-watson/issues/87)

* v6.0.0 cb02879 -- 2024-08-20
* Fix: show score and severity in dependency-check findings [#58](https://github.com/clj-holmes/clj-watson/issues/58)
Expand Down
2 changes: 1 addition & 1 deletion deps.edn
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
:main-opts ["-m" "clojure-lsp.main"]}
:test {:extra-paths ["test"]
:extra-deps {lambdaisland/kaocha {:mvn/version "1.91.1392"}}
:main-opts ["-m" "kaocha.runner"]}
:main-opts ["-m" "kaocha.runner" "--reporter" "documentation"]}

;; for dev: so we can run the recommended command from the README:
:clj-watson {:replace-deps {io.github.clj-holmes/clj-watson {:local/root "."}}
Expand Down
27 changes: 17 additions & 10 deletions src/clj_watson/controller/dependency_check/scanner.clj
Original file line number Diff line number Diff line change
Expand Up @@ -96,16 +96,20 @@
(defn ^:private build-engine [settings]
(Engine. settings))

(defn ^:private clojure-file? [dependency-path]
(defn ^:private jar-file? [dependency-path]
(string/ends-with? dependency-path ".jar"))

(defn ^:private scan-jars [engine dependencies]
(defn ^:private deps->jars [dependencies]
(->> dependencies
;; as far as I understand, a dep will only ever point to a single jar
;; but we exclude non-jar deps and perhaps local paths
(map :paths)
(apply concat)
(filter clojure-file?)
(map io/file)
(.scan engine))
(filter jar-file?)
(map io/file)))

(defn ^:private scan-jars [engine jars]
(.scan engine jars)
(.analyzeDependencies engine)
engine)

Expand All @@ -114,8 +118,11 @@
(let [settings (create-settings dependency-check-properties clj-watson-properties)]
(when-let [{:keys [exit]} (validate-settings settings opts)]
(System/exit exit))
(with-open [engine (build-engine settings)]
(-> engine
(scan-jars dependencies)
(.getDependencies)
(Arrays/asList)))))
(let [jars (deps->jars dependencies)
vulnerable-jars (with-open [engine (build-engine settings)]
(-> engine
(scan-jars jars)
(.getDependencies)
(Arrays/asList)))]
{:deps-scanned (count jars)
:findings vulnerable-jars})))
7 changes: 4 additions & 3 deletions src/clj_watson/controller/github/vulnerability.clj
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,10 @@

(defn scan-dependencies
[dependencies repositories allow-list]
(->> dependencies
(pmap (partial scan-dependency repositories allow-list))
(filterv :vulnerabilities)))
{:deps-scanned (count dependencies)
:findings (->> dependencies
(pmap (partial scan-dependency repositories allow-list))
(filterv :vulnerabilities))})

(comment
(def repositories {:mvn/repos {"central" {:url "https://repo1.maven.org/maven2/"}
Expand Down
29 changes: 28 additions & 1 deletion src/clj_watson/controller/output.clj
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
[clj-watson.logic.sarif :as logic.sarif]
[clj-watson.logic.template :as logic.template]
[clojure.java.io :as io]
[clojure.pprint :as pprint]))
[clojure.pprint :as pprint]
[clojure.string :as str]))

(defmulti ^:private generate* (fn [_ _ kind] kind))

Expand All @@ -27,3 +28,29 @@

(defn generate [dependencies deps-edn-path kind]
(generate* dependencies deps-edn-path kind))

(defn final-summary
"See `clj-waston.logic.summarize/summary` for description"
[{:keys [cnt-deps-scanned cnt-deps-vulnerable
cnt-deps-severities cnt-deps-unexpected-severities cnt-deps-unspecified-severity]}]
(let [details (->> [(when (seq cnt-deps-severities)
(format "(%s)"
(->> cnt-deps-severities
(mapv (fn [[severity count]] (format "%d %s" count severity)))
(str/join ", "))))
(when (seq cnt-deps-unexpected-severities)
(format "Unrecognized severities: (%s)"
(->> cnt-deps-unexpected-severities
(mapv (fn [[severity count]] (format "%d %s" count severity)))
(str/join ", "))))
(when (and cnt-deps-unspecified-severity (> cnt-deps-unspecified-severity 0))
(format "Unspecified severities: %d" cnt-deps-unspecified-severity))]
(keep identity))]
;; Dependencies scanned: 151
;; Vulnerable dependencies found: 9 (4 Critical, 1 High, 2 Medium), Unrecognize d severities: (2 Foobar), Unspecified severities: 3
(println (format "Dependencies scanned: %d" cnt-deps-scanned))
(println (format "Vulnerable dependencies found: %d%s"
cnt-deps-vulnerable
(if (seq details)
(str " " (str/join ", " details))
"")))))
74 changes: 34 additions & 40 deletions src/clj_watson/entrypoint.clj
Original file line number Diff line number Diff line change
Expand Up @@ -9,51 +9,42 @@
[clj-watson.controller.output :as controller.output]
[clj-watson.controller.remediate :as controller.remediate]
[clj-watson.logging-config :as logging-config]
[clj-watson.logic.summarize :as summarize]
[clojure.java.io :as io]
[clojure.tools.reader.edn :as edn]))

(defmulti scan* (fn [{:keys [database-strategy]}] database-strategy))

(defmethod scan* :github-advisory [{:keys [deps-edn-path suggest-fix aliases]}]
(let [{:keys [deps dependencies]} (controller.deps/parse deps-edn-path aliases)
repositories (select-keys deps [:mvn/repos])
config (when-let [config-file (io/resource "clj-watson-config.edn")]
(defmethod scan* :github-advisory [{:keys [dependencies repositories]}]
(let [config (when-let [config-file (io/resource "clj-watson-config.edn")]
(edn/read-string (slurp config-file)))
allow-list (adapter.config/config->allow-config-map config)
vulnerable-dependencies (controller.gh.vulnerability/scan-dependencies dependencies repositories allow-list)]
(if suggest-fix
(controller.remediate/scan vulnerable-dependencies deps)
vulnerable-dependencies)))
allow-list (adapter.config/config->allow-config-map config)]
(controller.gh.vulnerability/scan-dependencies dependencies repositories allow-list)))

(defmethod scan* :dependency-check [{:keys [deps-edn-path suggest-fix aliases
dependency-check-properties clj-watson-properties] :as opts}]
;; dependency-check uses Apache Commons JCS, ask it to use log4j2 to allow us to configure its noisy logging
(System/setProperty "jcs.logSystem" "log4j2")
(let [{:keys [deps dependencies]} (controller.deps/parse deps-edn-path aliases)
repositories (select-keys deps [:mvn/repos])
scanned-dependencies (controller.dc.scanner/start! dependencies
dependency-check-properties
clj-watson-properties
opts)
vulnerable-dependencies (controller.dc.vulnerability/extract scanned-dependencies dependencies repositories)]
(if suggest-fix
(controller.remediate/scan vulnerable-dependencies deps)
vulnerable-dependencies)))
(defmethod scan* :dependency-check [{:keys [dependency-check-properties clj-watson-properties
dependencies repositories] :as opts}]
(let [{:keys [findings] :as result}
(controller.dc.scanner/start! dependencies
dependency-check-properties
clj-watson-properties
opts)]
(assoc result :findings (controller.dc.vulnerability/extract findings dependencies repositories))))

(defmethod scan* :default [opts]
(scan* (assoc opts :database-strategy "dependency-check")))
(scan* (assoc opts :database-strategy :dependency-check)))

(defn do-scan
"Indirect entry point for -M usage."
[opts]
(defn do-scan [{:keys [fail-on-result output deps-edn-path aliases suggest-fix] :as opts}]
(logging-config/init)
(let [{:keys [fail-on-result output deps-edn-path]} opts
vulnerabilities (scan* opts)
contains-vulnerabilities? (->> vulnerabilities
(map (comp empty? :vulnerabilities))
(some false?))]
(controller.output/generate vulnerabilities deps-edn-path output)
(if (and contains-vulnerabilities? fail-on-result)
(let [{:keys [deps dependencies]} (controller.deps/parse deps-edn-path aliases)
repositories (select-keys deps [:mvn/repos])
{:keys [findings] :as result} (scan* (assoc opts
:dependencies dependencies
:repositories repositories))
findings (if suggest-fix
(controller.remediate/scan findings deps)
findings)]
(controller.output/generate findings deps-edn-path output)
(-> result summarize/summarize controller.output/final-summary)
(if (and (seq findings) fail-on-result)
(System/exit 1)
(System/exit 0))))

Expand All @@ -64,11 +55,14 @@
(do-scan (cli-spec/validate-tool-opts opts)))

(comment
(def vulnerabilities (scan* {:deps-edn-path "resources/vulnerable-deps.edn"
:database-strategy "dependency-check"
:suggest-fix true}))
(def vulnerabilities (do-scan {:deps-edn-path "resources/vulnerable-deps.edn"
:database-strategy :dependency-check
:suggest-fix true}))

(def vulnerabilities (scan* {:deps-edn-path "resources/vulnerable-deps.edn"
:database-strategy "github-advisory"}))
:database-strategy :github-advisory}))

(controller.output/generate vulnerabilities "deps.edn" "sarif")
(controller.output/generate vulnerabilities "deps.edn" "stdout-simple"))
(controller.output/generate vulnerabilities "deps.edn" "stdout-simple")

:eoc)
2 changes: 2 additions & 0 deletions src/clj_watson/logging_config.clj
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
(defn init
"Complement `resources/logaback.xml` with some customizations"
[]
;; dependency-check uses Apache Commons JCS, ask it to use log4j2 to allow us to configure its noisy logging
(System/setProperty "jcs.logSystem" "log4j2")
(.addTurboFilter (LoggerFactory/getILoggerFactory) (create-custom-filter)))

(comment
Expand Down

0 comments on commit b17e1b9

Please sign in to comment.