From 100825acc843eee8a0ffc818782df345e86a0a3d Mon Sep 17 00:00:00 2001 From: jamesthompson26-nhs Date: Wed, 17 Dec 2025 14:27:49 +0000 Subject: [PATCH 1/5] CCM-13343_Trivy_Package_and_Library_Scans --- .github/actions/trivy-iac/action.yaml | 18 ++ .github/actions/trivy-package/action.yaml | 16 ++ .github/actions/trivy/action.yaml | 17 -- .github/workflows/stage-1-commit.yaml | 27 ++- scripts/terraform/trivy-scan.sh | 194 ++++++++++++++++++++++ scripts/terraform/trivy.sh | 96 ----------- 6 files changed, 250 insertions(+), 118 deletions(-) create mode 100644 .github/actions/trivy-iac/action.yaml create mode 100644 .github/actions/trivy-package/action.yaml delete mode 100644 .github/actions/trivy/action.yaml create mode 100755 scripts/terraform/trivy-scan.sh delete mode 100755 scripts/terraform/trivy.sh diff --git a/.github/actions/trivy-iac/action.yaml b/.github/actions/trivy-iac/action.yaml new file mode 100644 index 00000000..583f9356 --- /dev/null +++ b/.github/actions/trivy-iac/action.yaml @@ -0,0 +1,18 @@ +name: "Trivy IaC Scan" +description: "Scan Terraform IaC using Trivy" +runs: + using: "composite" + steps: + - name: "Trivy Terraform IaC Scan" + shell: bash + run: | + components_exit_code=0 + modules_exit_code=0 + + ./scripts/terraform/trivy-scan.sh --mode iac ./infrastructure/terraform/components || components_exit_code=$? + ./scripts/terraform/trivy-scan.sh --mode iac ./infrastructure/terraform/modules || modules_exit_code=$? + + if [ $components_exit_code -ne 0 ] || [ $modules_exit_code -ne 0 ]; then + echo "Trivy misconfigurations detected." + exit 1 + fi diff --git a/.github/actions/trivy-package/action.yaml b/.github/actions/trivy-package/action.yaml new file mode 100644 index 00000000..d6ee4a3f --- /dev/null +++ b/.github/actions/trivy-package/action.yaml @@ -0,0 +1,16 @@ +name: "Trivy Package Scan" +description: "Scan project packages using Trivy" +runs: + using: "composite" + steps: + - name: "Trivy Package Scan" + shell: bash + run: | + exit_code=0 + + ./scripts/terraform/trivy-scan.sh --mode package . || exit_code=$? + + if [ $exit_code -ne 0 ]; then + echo "Trivy has detected package vulnerablilites. Please refer to https://nhsd-confluence.digital.nhs.uk/spaces/RIS/pages/1257636917/PLAT-KOP-012+-+Trivy+Pipeline+Vulnerability+Scanning+Exemption" + exit 1 + fi diff --git a/.github/actions/trivy/action.yaml b/.github/actions/trivy/action.yaml deleted file mode 100644 index be940ce5..00000000 --- a/.github/actions/trivy/action.yaml +++ /dev/null @@ -1,17 +0,0 @@ -name: "Trivy Scan" -runs: - using: "composite" - steps: - - name: "Trivy Terraform IAC Scan" - shell: bash - run: | - components_exit_code=0 - modules_exit_code=0 - - ./scripts/terraform/trivy.sh ./infrastructure/terraform/components || components_exit_code=$? - ./scripts/terraform/trivy.sh ./infrastructure/terraform/modules || modules_exit_code=$? - - if [ $components_exit_code -ne 0 ] || [ $modules_exit_code -ne 0 ]; then - echo "Trivy misconfigurations detected." - exit 1 - fi diff --git a/.github/workflows/stage-1-commit.yaml b/.github/workflows/stage-1-commit.yaml index ececcb36..20bc3537 100644 --- a/.github/workflows/stage-1-commit.yaml +++ b/.github/workflows/stage-1-commit.yaml @@ -146,10 +146,12 @@ jobs: uses: actions/checkout@v4 - name: "Lint Terraform" uses: ./.github/actions/lint-terraform - trivy: - name: "Trivy Scan" + trivy-iac: + name: "Trivy IaC Scan" + permissions: + contents: read runs-on: ubuntu-latest - timeout-minutes: 5 + timeout-minutes: 10 needs: detect-terraform-changes if: needs.detect-terraform-changes.outputs.terraform_changed == 'true' steps: @@ -159,8 +161,23 @@ jobs: uses: asdf-vm/actions/setup@v4 - name: "Perform Setup" uses: ./.github/actions/setup - - name: "Trivy Scan" - uses: ./.github/actions/trivy + - name: "Trivy IaC Scan" + uses: ./.github/actions/trivy-iac + trivy-package: + name: "Trivy Package Scan" + permissions: + contents: read + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: "Checkout code" + uses: actions/checkout@v4 + - name: "Setup ASDF" + uses: asdf-vm/actions/setup@v4 + - name: "Perform Setup" + uses: ./.github/actions/setup + - name: "Trivy Package Scan" + uses: ./.github/actions/trivy-package count-lines-of-code: name: "Count lines of code" runs-on: ubuntu-latest diff --git a/scripts/terraform/trivy-scan.sh b/scripts/terraform/trivy-scan.sh new file mode 100755 index 00000000..15656233 --- /dev/null +++ b/scripts/terraform/trivy-scan.sh @@ -0,0 +1,194 @@ +#!/usr/bin/env bash + +# WARNING: Please DO NOT edit this file! It is maintained in the Repository Template (https://github.com/NHSDigital/nhs-notify-repository-template). Raise a PR instead. + +set -euo pipefail + +function usage() { + cat <<'EOF' +Usage: ./scripts/terraform/trivy-scan.sh --mode [directory] + +Options: + --mode, -m Scan type to run. Accepts "iac" or "package" (required). + --help, -h Show this message. + [directory] Directory to scan. Defaults to the repository root. + +Environment variables: + FORCE_USE_DOCKER=true Force execution through Docker even if Trivy is installed locally. + VERBOSE=true Enable bash -x tracing. +EOF +} + +function main() { + cd "$(git rev-parse --show-toplevel)" + + local scan_mode="" + local dir_to_scan="." + + while [[ $# -gt 0 ]]; do + case "$1" in + --mode|-m) + if [[ $# -lt 2 ]]; then + echo "Error: --mode requires an argument." >&2 + usage + exit 1 + fi + scan_mode="$2" + shift 2 + ;; + --help|-h) + usage + exit 0 + ;; + --) + shift + break + ;; + -*) + echo "Unknown option: $1" >&2 + usage + exit 1 + ;; + *) + dir_to_scan="$1" + shift + ;; + esac + done + + if [[ $# -gt 0 ]]; then + dir_to_scan="$1" + fi + + if [[ -z "$scan_mode" ]]; then + echo "Error: --mode must be provided (iac|package)." >&2 + usage + exit 1 + fi + + case "$scan_mode" in + iac|package) + ;; + *) + echo "Error: unknown mode '$scan_mode'. Expected 'iac' or 'package'." >&2 + usage + exit 1 + ;; + esac + + if command -v trivy > /dev/null 2>&1 && ! is-arg-true "${FORCE_USE_DOCKER:-false}"; then + run-trivy-natively "$scan_mode" "$dir_to_scan" + else + run-trivy-in-docker "$scan_mode" "$dir_to_scan" + fi +} + +function run-trivy-natively() { + local scan_mode="$1" + local dir_to_scan="$2" + + echo "Trivy found locally, running natively" + echo "Running Trivy ($scan_mode) on directory: $dir_to_scan" + + if execute-trivy-command "$scan_mode" "$dir_to_scan"; then + check-trivy-status 0 + else + local status=$? + check-trivy-status "$status" + fi +} + +function run-trivy-in-docker() { + # shellcheck disable=SC1091 + source ./scripts/docker/docker.lib.sh + + local scan_mode="$1" + local dir_to_scan="$2" + + # shellcheck disable=SC2155 + local image=$(name=aquasec/trivy docker-get-image-version-and-pull) + + echo "Trivy not found locally, running in Docker Container" + echo "Running Trivy ($scan_mode) on directory: $dir_to_scan" + + if execute-trivy-in-docker "$image" "$scan_mode" "$dir_to_scan"; then + check-trivy-status 0 + else + local status=$? + check-trivy-status "$status" + fi +} + +function execute-trivy-command() { + local scan_mode="$1" + local dir_to_scan="$2" + + if [[ "$scan_mode" == "iac" ]]; then + trivy config \ + --config scripts/config/trivy.yaml \ + --tf-exclude-downloaded-modules \ + "$dir_to_scan" + else + trivy \ + --config scripts/config/trivy.yaml \ + fs "$dir_to_scan" \ + --scanners vuln \ + --severity HIGH,CRITICAL \ + --include-dev-deps + fi +} + +function execute-trivy-in-docker() { + local image="$1" + local scan_mode="$2" + local dir_to_scan="$3" + + if [[ "$scan_mode" == "iac" ]]; then + docker run --rm --platform linux/amd64 \ + --volume "$PWD":/workdir \ + --workdir /workdir \ + "$image" \ + config \ + --config scripts/config/trivy.yaml \ + --tf-exclude-downloaded-modules \ + "$dir_to_scan" + else + docker run --rm --platform linux/amd64 \ + --volume "$PWD":/workdir \ + --workdir /workdir \ + "$image" \ + --config scripts/config/trivy.yaml \ + fs "$dir_to_scan" \ + --scanners vuln \ + --severity HIGH,CRITICAL \ + --include-dev-deps + fi +} + +function check-trivy-status() { + local status="$1" + + if [[ "$status" -eq 0 ]]; then + echo "Trivy completed successfully." + return 0 + fi + + echo "Trivy found issues." + exit "$status" +} + +function is-arg-true() { + if [[ "$1" =~ ^(true|yes|y|on|1|TRUE|YES|Y|ON)$ ]]; then + return 0 + else + return 1 + fi +} + +# ============================================================================== + +is-arg-true "${VERBOSE:-false}" && set -x + +main "$@" + +exit 0 diff --git a/scripts/terraform/trivy.sh b/scripts/terraform/trivy.sh deleted file mode 100755 index 93caabd8..00000000 --- a/scripts/terraform/trivy.sh +++ /dev/null @@ -1,96 +0,0 @@ -#!/usr/bin/env bash - -# WARNING: Please DO NOT edit this file! It is maintained in the Repository Template (https://github.com/NHSDigital/nhs-notify-repository-template). Raise a PR instead. - -set -euo pipefail - -# TFSec command wrapper. It will run the command natively if TFSec is -# installed, otherwise it will run it in a Docker container. -# Run tfsec for security checks on Terraform code. -# -# Usage: -# $ ./trivy.sh [directory] -# ============================================================================== - -function main() { - - cd "$(git rev-parse --show-toplevel)" - - local dir_to_scan=${1:-.} - - if command -v trivy > /dev/null 2>&1 && ! is-arg-true "${FORCE_USE_DOCKER:-false}"; then - # shellcheck disable=SC2154 - run-trivy-natively "$dir_to_scan" - else - run-trivy-in-docker "$dir_to_scan" - fi -} - -# Run trivy on the specified directory. -# Arguments: -# $1 - Directory to scan -function run-trivy-natively() { - - local dir_to_scan="$1" - - echo "Trivy found locally, running natively" - - echo "Running Trivy on directory: $dir_to_scan" - trivy config \ - --config scripts/config/trivy.yaml \ - --tf-exclude-downloaded-modules \ - "${dir_to_scan}" - - check-trivy-status -} - -# Check the exit status of tfsec. -function check-trivy-status() { - - if [ $? -eq 0 ]; then - echo "Trivy completed successfully." - else - echo "Trivy found issues." - exit 1 - fi -} - -function run-trivy-in-docker() { - - # shellcheck disable=SC1091 - source ./scripts/docker/docker.lib.sh - local dir_to_scan="$1" - - # shellcheck disable=SC2155 - local image=$(name=aquasec/trivy docker-get-image-version-and-pull) - # shellcheck disable=SC2086 - echo "Trivy not found locally, running in Docker Container" - echo "Running Trivy on directory: $dir_to_scan" - docker run --rm --platform linux/amd64 \ - --volume "$PWD":/workdir \ - --workdir /workdir \ - "$image" \ - config \ - --config scripts/config/trivy.yaml \ - --tf-exclude-downloaded-modules \ - "${dir_to_scan}" - check-trivy-status -} -# ============================================================================== - -function is-arg-true() { - - if [[ "$1" =~ ^(true|yes|y|on|1|TRUE|YES|Y|ON)$ ]]; then - return 0 - else - return 1 - fi -} - -# ============================================================================== - -is-arg-true "${VERBOSE:-false}" && set -x - -main "$@" - -exit 0 From 1bc20333f922458609206f0cdbbdfb05e7348844 Mon Sep 17 00:00:00 2001 From: jamesthompson26-nhs Date: Thu, 18 Dec 2025 12:57:04 +0000 Subject: [PATCH 2/5] CCM-13343: Trivy Package and Library Scans --- docs/Gemfile | 2 +- docs/Gemfile.lock | 91 ++++++++++++------- .../adr/assets/ADR-003/examples/golang/go.mod | 2 +- 3 files changed, 58 insertions(+), 37 deletions(-) diff --git a/docs/Gemfile b/docs/Gemfile index e4ff8d82..a1cd6427 100644 --- a/docs/Gemfile +++ b/docs/Gemfile @@ -7,7 +7,7 @@ source "https://rubygems.org" # # This will help ensure the proper Jekyll version is running. # Happy Jekylling! -gem "jekyll", "~> 4.3.3" +gem "jekyll", "~> 4.4.1" # This is the default theme for new Jekyll sites. You may change this to anything you like. gem "minima", "~> 2.5" # If you want to use GitHub Pages, remove the "gem "jekyll"" above and diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock index 100d3efd..8b1dfde9 100644 --- a/docs/Gemfile.lock +++ b/docs/Gemfile.lock @@ -1,54 +1,65 @@ GEM remote: https://rubygems.org/ specs: - activesupport (7.1.3.4) + activesupport (8.1.1) base64 bigdecimal - concurrent-ruby (~> 1.0, >= 1.0.2) + concurrent-ruby (~> 1.0, >= 1.3.1) connection_pool (>= 2.2.5) drb i18n (>= 1.6, < 2) + json + logger (>= 1.4.2) minitest (>= 5.1) - mutex_m - tzinfo (~> 2.0) - addressable (2.8.6) - public_suffix (>= 2.0.2, < 6.0) - base64 (0.2.0) - bigdecimal (3.1.8) - cgi (0.4.2) + securerandom (>= 0.3) + tzinfo (~> 2.0, >= 2.0.5) + uri (>= 0.13.1) + addressable (2.8.8) + public_suffix (>= 2.0.2, < 8.0) + base64 (0.3.0) + bigdecimal (4.0.1) + cgi (0.5.1) colorator (1.1.0) - concurrent-ruby (1.2.3) - connection_pool (2.4.1) - drb (2.2.1) + concurrent-ruby (1.3.6) + connection_pool (3.0.2) + csv (3.3.5) + drb (2.2.3) em-websocket (0.5.3) eventmachine (>= 0.12.9) http_parser.rb (~> 0) erb (4.0.4) cgi (>= 0.3.3) eventmachine (1.2.7) - ffi (1.16.3) + ffi (1.17.2-arm64-darwin) + ffi (1.17.2-x86_64-linux-gnu) forwardable-extended (2.6.0) gemoji (4.1.0) - google-protobuf (4.29.2-x86_64-linux) + google-protobuf (4.33.2-arm64-darwin) + bigdecimal + rake (>= 13) + google-protobuf (4.33.2-x86_64-linux-gnu) bigdecimal rake (>= 13) html-pipeline (2.14.3) activesupport (>= 2) nokogiri (>= 1.4) http_parser.rb (0.8.0) - i18n (1.14.5) + i18n (1.14.7) concurrent-ruby (~> 1.0) - jekyll (4.3.3) + jekyll (4.4.1) addressable (~> 2.4) + base64 (~> 0.2) colorator (~> 1.0) + csv (~> 3.0) em-websocket (~> 0.5) i18n (~> 1.0) jekyll-sass-converter (>= 2.0, < 4.0) jekyll-watch (~> 2.0) + json (~> 2.6) kramdown (~> 2.3, >= 2.3.1) kramdown-parser-gfm (~> 1.0) liquid (~> 4.0) - mercenary (>= 0.3.6, < 0.5) + mercenary (~> 0.3, >= 0.3.6) pathutil (~> 0.9) rouge (>= 3.0, < 5.0) safe_yaml (~> 1.0) @@ -62,8 +73,8 @@ GEM jekyll (>= 3.7, < 5.0) jekyll-include-cache (0.2.1) jekyll (>= 3.7, < 5.0) - jekyll-sass-converter (3.0.0) - sass-embedded (~> 1.54) + jekyll-sass-converter (3.1.0) + sass-embedded (~> 1.75) jekyll-seo-tag (2.8.0) jekyll (>= 3.8, < 5.0) jekyll-watch (2.2.1) @@ -72,54 +83,64 @@ GEM gemoji (>= 3, < 5) html-pipeline (~> 2.2) jekyll (>= 3.0, < 5.0) - just-the-docs (0.8.2) + json (2.18.0) + just-the-docs (0.10.1) jekyll (>= 3.8.5) jekyll-include-cache jekyll-seo-tag (>= 2.0) rake (>= 12.3.1) - kramdown (2.4.0) - rexml + kramdown (2.5.1) + rexml (>= 3.3.9) kramdown-parser-gfm (1.1.0) kramdown (~> 2.0) liquid (4.0.4) listen (3.9.0) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) + logger (1.7.0) mercenary (0.4.0) - minima (2.5.1) + minima (2.5.2) jekyll (>= 3.5, < 5.0) jekyll-feed (~> 0.9) jekyll-seo-tag (~> 2.1) - minitest (5.24.1) - mutex_m (0.2.0) - nokogiri (1.18.9-x86_64-linux-gnu) + minitest (6.0.0) + prism (~> 1.5) + nokogiri (1.18.10-arm64-darwin) + racc (~> 1.4) + nokogiri (1.18.10-x86_64-linux-gnu) racc (~> 1.4) pathutil (0.16.2) forwardable-extended (~> 2.6) - public_suffix (5.0.5) + prism (1.6.0) + public_suffix (7.0.0) racc (1.8.1) - rake (13.2.1) + rake (13.3.1) rb-fsevent (0.11.2) rb-inotify (0.11.1) ffi (~> 1.0) - rexml (3.3.9) - rouge (4.2.1) + rexml (3.4.4) + rouge (4.6.1) safe_yaml (1.0.5) - sass-embedded (1.83.0-x86_64-linux-gnu) - google-protobuf (~> 4.28) + sass-embedded (1.97.0-arm64-darwin) + google-protobuf (~> 4.31) + sass-embedded (1.97.0-x86_64-linux-gnu) + google-protobuf (~> 4.31) + securerandom (0.4.1) terminal-table (3.0.2) unicode-display_width (>= 1.1.1, < 3) tzinfo (2.0.6) concurrent-ruby (~> 1.0) - unicode-display_width (2.5.0) - webrick (1.8.2) + unicode-display_width (2.6.0) + uri (1.1.1) + webrick (1.9.2) PLATFORMS + arm64-darwin-25 x86_64-linux DEPENDENCIES http_parser.rb (~> 0.6.0) - jekyll (~> 4.3.3) + jekyll (~> 4.4.1) jekyll-drawio jekyll-feed (~> 0.12) jemoji diff --git a/docs/adr/assets/ADR-003/examples/golang/go.mod b/docs/adr/assets/ADR-003/examples/golang/go.mod index 5e8c921c..34a82f5b 100644 --- a/docs/adr/assets/ADR-003/examples/golang/go.mod +++ b/docs/adr/assets/ADR-003/examples/golang/go.mod @@ -5,7 +5,7 @@ toolchain go1.24.1 require ( github.com/go-resty/resty/v2 v2.7.0 - github.com/golang-jwt/jwt v3.2.2+incompatible + github.com/golang-jwt/jwt v5.3.0+incompatible ) require golang.org/x/net v0.38.0 // indirect From a6a938b19c4b92d2866f08ef72b7bb76e4ce5b63 Mon Sep 17 00:00:00 2001 From: jamesthompson26-nhs Date: Mon, 22 Dec 2025 11:11:34 +0000 Subject: [PATCH 3/5] CCM-13343: Trivy Package and Library Scans --- scripts/config/trivy.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/config/trivy.yaml b/scripts/config/trivy.yaml index a4eff466..29707dbb 100644 --- a/scripts/config/trivy.yaml +++ b/scripts/config/trivy.yaml @@ -4,3 +4,4 @@ exit-code: 1 # When issues are found scan: skip-files: - "**/.terraform/**/*" + - "**/node_modules/**/*" From e010c56c543fada5d70254b8e86a9c6477c61425 Mon Sep 17 00:00:00 2001 From: jamesthompson26-nhs Date: Tue, 23 Dec 2025 10:10:39 +0000 Subject: [PATCH 4/5] CCM-13343: Trivy Package and Library Scans --- .github/workflows/stage-1-commit.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/stage-1-commit.yaml b/.github/workflows/stage-1-commit.yaml index 20bc3537..ee3da309 100644 --- a/.github/workflows/stage-1-commit.yaml +++ b/.github/workflows/stage-1-commit.yaml @@ -158,7 +158,7 @@ jobs: - name: "Checkout code" uses: actions/checkout@v4 - name: "Setup ASDF" - uses: asdf-vm/actions/setup@v4 + uses: asdf-vm/actions/setup@1902764435ca0dd2f3388eea723a4f92a4eb8302 - name: "Perform Setup" uses: ./.github/actions/setup - name: "Trivy IaC Scan" @@ -173,7 +173,7 @@ jobs: - name: "Checkout code" uses: actions/checkout@v4 - name: "Setup ASDF" - uses: asdf-vm/actions/setup@v4 + uses: asdf-vm/actions/setup@1902764435ca0dd2f3388eea723a4f92a4eb8302 - name: "Perform Setup" uses: ./.github/actions/setup - name: "Trivy Package Scan" From 74fd2925a5e0218ab48be796628bd6d9ca9509df Mon Sep 17 00:00:00 2001 From: jamesthompson26-nhs Date: Tue, 23 Dec 2025 10:50:55 +0000 Subject: [PATCH 5/5] CCM-13343: Trivy Package and Library Scans --- .tool-versions | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.tool-versions b/.tool-versions index 63bba7da..389f264d 100644 --- a/.tool-versions +++ b/.tool-versions @@ -13,8 +13,8 @@ python 3.13.2 # The section below is reserved for Docker image versions. # TODO: Move this section - consider using a different file for the repository template dependencies. -# docker/ghcr.io/anchore/grype v0.92.2@sha256:651e558f9ba84f2a790b3449c8a57cbbf4f34e004f7d3f14ae8f8cbeede4cd33 # SEE: https://github.com/anchore/grype/pkgs/container/grype -# docker/ghcr.io/anchore/syft v1.26.0@sha256:de078f51704a213906970b1475edd6006b8af50aa159852e125518237487b8c6 # SEE: https://github.com/anchore/syft/pkgs/container/syft +# docker/ghcr.io/anchore/grype v0.104.3@sha256:d340f4f8b3b7e6e72a6c9c0152f25402ed8a2d7375dba1dfce4e53115242feb6 # SEE: https://github.com/anchore/grype/pkgs/container/grype +# docker/ghcr.io/anchore/syft v1.39.0@sha256:6f13bb010923c33fb197047c8f88888e77071bd32596b3f605d62a133e493ce4 # SEE: https://github.com/anchore/syft/pkgs/container/syft # docker/ghcr.io/gitleaks/gitleaks:v8.24.0@sha256:b8e9bf46893c2f20e10bfb4b2e783adaef519dea981b01ca6221ac325e836040 # SEE: https://github.com/gitleaks/gitleaks/pkgs/container/gitleaks # docker/ghcr.io/igorshubovych/markdownlint-cli v0.37.0@sha256:fb3e79946fce78e1cde84d6798c6c2a55f2de11fc16606a40d49411e281d950d # SEE: https://github.com/igorshubovych/markdownlint-cli/pkgs/container/markdownlint-cli # docker/ghcr.io/make-ops-tools/gocloc latest@sha256:6888e62e9ae693c4ebcfed9f1d86c70fd083868acb8815fe44b561b9a73b5032 # SEE: https://github.com/make-ops-tools/gocloc/pkgs/container/gocloc