Skip to content

clj-holmes/clj-watson

Repository files navigation

clj-watson

A Clojure tool for vulnerability checking.

clj-watson is a software composition analysis (SCA) tool, that scans dependencies in a Clojure deps.edn file looking for vulnerable direct and transitive dependencies, and builds a report with all the information needed to help you understand how the vulnerabilities manifest in your software.

clj-watson can suggest a remediation for the vulnerabilities found, and can check against both the NIST National Vulnerability Database (NVD) (by default) and the GitHub Advisory Database (experimental).

Quick Start

clj-watson can be added as an alias either on a per-project basis in the project's deps.edn file or in your user deps.edn file (either ~/.clojure/deps.edn or ~/.config/clojure/deps.edn):

  ;; in :aliases
  :clj-watson {:replace-deps
               {io.github.clj-holmes/clj-watson
                {:git/tag "v5.1.1" :git/sha "ad5fe07"}}
               :main-opts ["-m" "clj-watson.cli" "scan"]}

Then you can run it with:

clojure -M:clj-watson -p deps.edn

The first time it runs, it will download the vulnerability database, which can take a few minutes. Subsequent runs will be much faster.

Note: the database is stored in the /tmp/db/ folder (on macOS/Linux) - in case you ever need to delete that folder, if it looks like the database is corrupted.

It can also be installed as a Clojure CLI tool:

clojure -Ttools install-latest :lib io.github.clj-holmes/clj-watson :as clj-watson

Then run it with:

clojure -Tclj-watson scan :deps-edn-path '"deps.edn"' :output '"stdout"'
# or:
clojure -Tclj-watson scan '{:deps-edn-path "deps.edn" :output "stdout"}'

The tool option keywords match the long-form CLI option names (see below) but the abbreviations are also supported. In addition, any string option may be specified as a bare Clojure symbol (if it is legally representable as such), which means the above command-line can be simplified to just:

clojure -Tclj-watson scan :p deps.edn

:output can be omitted because it defaults to stdout, and :deps-edn-path can be shortened to :p (matching the -p short form of --deps-edn-path).

Note: :aliases (or :a) should be specified as a vector of keywords (or symbols), e.g., :a '[:foo :bar], whereas it would be specified multiple times (as strings) in the regular CLI, -a foo -a bar.

How it works

Vulnerability database strategies

clj-watson supports two methods for vulnerabilities scan.

DependencyCheck

DependencyCheck is the most widely used method among the Clojure/Java SCA tools. It downloads all vulnerabilities from NVD and stores it in a database (located in the /tmp/db/ folder), composes a Common Platform Enumeration (CPE) based on the dependencies, scans all JARs in the classpath and matches vulnerabilities using it.

  • clj-watson v5.x.x uses DependencyCheck 9.0.x and the new NIST NVD API.
  • clj-watson v4.x.x uses an earlier version of DependencyCheck and the old NVD data feeds, which have been deprecated.

NIST NVD API

As of version v5.0.0, clj-watson uses DependencyCheck 9.0.x which switches from the earlier NVD data feeds to the new NIST NVD API.

This new API heavily throttles anonymous requests, so it is highly recommended to get an API key in order to use the API efficiently.

Read the NIST NVD announcement for more information.

Once you have an API key, you can provide it to clj-watson via the nvd.api.key property in the optional clj-watson.properties file, either on the classpath you use to run clj-watson or via the -w / --clj-watson-properties command-line option:

# clj-watson.properties file
nvd.api.key=...your key here...

GitHub Advisory Database [experimental]

This approach doesn't need to download a database since it uses the GitHub Advisory Database via its GraphQL API, and matches are made via package names.

In order to use this approach, it is necessary to generate a GitHub Personal Access Token (PAT) to access the GraphQL API, or if you use GitHub Actions it is possible to use their GitHub token.

Another important thing to be aware of is that the API has a limit of 5,000 requests per hour/per PAT.

If you create a PAT or use the GitHub Action token, you can set it as an environment variable named GITHUB_TOKEN and clj-watson will be able to use it.

Allow Listing Known CVE's

Sometimes the transitive dependency tree is not under your control and it is not always possible to override versions of dependencies that are vulnerable. You can allow a CVE for a limited period by adding a clj-watson-config.edn configuration file to your classpath with the following structure:

{:allow-list {:cves [{:cve-label "CVE-0000"
                      :expires "2000-01-01"}
                     {:cve-label "CVE-00000"
                      :expires "2000-01-01"}]}}

Note: this is for the GitHub Advisory Database strategy only.

Remediation suggestion

The big difference between clj-watson and other tools!

Since fixing the vulnerabilities found manually can be a truly frustrating process clj-watson provides a way to suggest a remediation.

It performs lookups for the whole dependency tree, checking if the latest version of a parent dependency uses the secure version of the child dependency until it reaches the direct dependency.

Given the following dependency tree,

[dependency-a "v1"]
  [dependency-b "v1"]
    [dependency-c "v1"]

where dependency-c is vulnerable and fixing it would require a bump from v1 to v2, clj-watson will try to find a version of dependency-a that uses a version of dependency-b that uses dependency-c at version v2, and then clj-watson will propose updating dependency-a.

{dependency-a {:mvn/version "v4"}}

If clj-watson does not find a version of dependency-b or dependency-a that satisfies this condition, it will propose an exclusion instead:

{dependency-a {:exclusions [dependency-b]}
 dependency-b {:mvn/version "v3"}}

In order to get the automated remediation suggestions, provide the --suggest-fix or -s option when running clj-watson.

Installation

clj-watson can be installed as a Clojure CLI tool, as shown above. While this is the easiest way to install the latest version and keep it up-to-date (using clojure -Ttools install-latest), it also means using the key/value EDN-style options for the CLI tool which can be a bit unwieldy as present:

clojure -Tclj-watson scan '{:output "stdout" :fail-on-result true :deps-edn-path "deps.edn" :suggest-fix true :aliases ["*"] :database-strategy "dependency-check"}'
# or:
clojure -Tclj-watson scan :f true :p deps.edn :s true :a '[*]'

Both :output (:o) and :database-strategy (:t) can be omitted because they default to "stdout" and "dependency-check" respectively.

In addition to the CLI tool install, shown above, it can also be invoked directly via the Clojure CLI, by specifying clj-watson as a dependency via -Sdeps:

clojure -Sdeps '{:deps {io.github.clj-holmes/clj-watson {:git/tag "v5.1.1" :git/sha "ad5fe07"}}}' -M -m clj-watson.cli scan -p deps.edn

Or you can just add it to your deps.edn file as an alias:

{:deps {}
 :aliases
 {:clj-watson {:extra-deps {io.github.clj-holmes/clj-watson {:git/tag "v5.1.1" :git/sha "ad5fe07"}}
               :main-opts ["-m" "clj-watson.cli" "scan"]}}}

and invoke it with:

clojure -M:clj-watson -p deps.edn

CLI Options

You can get a full list of the available options by running:

clojure -M:clj-watson scan -\?

This produces:

NAME:
 clj-watson scan - Performs a scan on a deps.edn file

USAGE:
 clj-watson scan [command options] [arguments...]

OPTIONS:
   -p, --deps-edn-path S*                                                      path of deps.edn to scan.
   -o, --output edn|json|sarif|stdout|stdout-simple          stdout            Output type.
   -a, --aliases S                                                             Specify a alias that will have the dependencies analysed alongside with the project deps.It's possible to provide multiple aliases. If a * is provided all the aliases are going to be analysed.
   -d, --dependency-check-properties S                                         [ONLY APPLIED IF USING DEPENDENCY-CHECK STRATEGY] Path of a dependency-check properties file. If not provided uses resources/dependency-check.properties.
   -w, --clj-watson-properties S                                               [ONLY APPLIED IF USING DEPENDENCY-CHECK STRATEGY] Path of an additional, optional properties file.
   -t, --database-strategy dependency-check|github-advisory  dependency-check  Vulnerability database strategy.
   -s, --[no-]suggest-fix                                    false             Suggest a new deps.edn file fixing all vulnerabilities found.
   -f, --[no-]fail-on-result                                 false             Enable or disable fail if results were found (useful for CI/CD).
   -?, --help

By default, when using the DEPENDENCY-CHECK strategy, clj-watson will load its own dependency-check.properties file, and then look for a clj-watson.properties file on the classpath and load that if found, for additional properties to apply to the DependencyCheck scan.

If you provide -d (or --dependency-check-properties) then clj-watson will load that file instead of its own dependency-check.properties file so it needs to be a complete properties file, not just the properties you want to override.

If you provide -w (or --clj-watson-properties) then clj-watson will load that file and apply those properties to the dependency-check scan. This is in addition to the properties loaded from the dependency-check.properties or the -d file. This can be useful to override just a few properties.

Execution

The minimum needed to run clj-watson is to provide the path to a deps.edn file, but it is recommended that you also provide the -s option so clj-watson will try to suggest a remediation for any vulnerabilities found.

clojure -M:clj-watson -p deps.edn
...
Downloading/Updating database.
Download/Update completed.
...

Dependency Information
-----------------------------------------------------
NAME: dependency-e
VERSION: 1

DEPENDENCY FOUND IN:

[dependency-a]
        [dependency-b]

[dependency-a]
        [dependency-c]
                [dependency-d]

FIX SUGGESTION: {dependency-a {:mvn/version "3"}}

Vulnerabilities
-----------------------------------------------------

SEVERITY: Information not available.
IDENTIFIERS: CVE-2022-1000000
CVSS: 7.5
PATCHED VERSION: 1.55

SEVERITY: Information not available.
IDENTIFIERS: CVE-2022-2000000
CVSS: 5.3
PATCHED VERSION: 1.55
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

Who uses it

Development

nREPL

clojure -M:nREPL -m nrepl.cmdline

Lint

clojure -M:clojure-lsp format
clojure -M:clojure-lsp clean-ns

License and Copyright

Copyright © 2021-2024 Matheus Bernardes

Distributed under the Eclipse Public License version 2.0.