From 62a319a8b0d4e92a8a081f383bc4e195b53c3a88 Mon Sep 17 00:00:00 2001 From: Tim Ireland Date: Fri, 22 May 2026 15:32:49 +0100 Subject: [PATCH 1/6] CCM-1725: updating action to fail on build and report package vulns Signed-off-by: Tim Ireland --- .github/actions/scan-dependencies/action.yaml | 24 +++++ scripts/reports/parse-vulnerabilities.sh | 89 +++++++++++++++++++ 2 files changed, 113 insertions(+) create mode 100755 scripts/reports/parse-vulnerabilities.sh diff --git a/.github/actions/scan-dependencies/action.yaml b/.github/actions/scan-dependencies/action.yaml index 44164c58..fb56f864 100644 --- a/.github/actions/scan-dependencies/action.yaml +++ b/.github/actions/scan-dependencies/action.yaml @@ -42,6 +42,13 @@ runs: run: | export BUILD_DATETIME=${{ inputs.build_datetime }} ./scripts/reports/scan-vulnerabilities.sh + - name: "Generate vulnerabilities summary" + shell: bash + run: | + ./scripts/reports/parse-vulnerabilities.sh vulnerabilities-repository-report.json | tee vulnerabilities-summary.md + if [[ -n "${GITHUB_STEP_SUMMARY:-}" ]]; then + cat vulnerabilities-summary.md >> "$GITHUB_STEP_SUMMARY" + fi - name: "Compress vulnerabilities report" shell: bash run: zip vulnerabilities-repository-report.json.zip vulnerabilities-repository-report.json @@ -52,6 +59,23 @@ runs: name: vulnerabilities-repository-report.json.zip path: ./vulnerabilities-repository-report.json.zip retention-days: 21 + - name: "Upload vulnerabilities summary as an artefact" + if: ${{ !env.ACT }} + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: vulnerabilities-summary.md + path: ./vulnerabilities-summary.md + retention-days: 21 + - name: "Fail if Critical or High vulnerabilities found" + shell: bash + run: | + CRITICAL_COUNT=$(jq '[.matches[] | select(.vulnerability.severity == "Critical")] | length' vulnerabilities-repository-report.json) + HIGH_COUNT=$(jq '[.matches[] | select(.vulnerability.severity == "High")] | length' vulnerabilities-repository-report.json) + echo "Critical: $CRITICAL_COUNT, High: $HIGH_COUNT" + if [[ "$CRITICAL_COUNT" -gt 0 || "$HIGH_COUNT" -gt 0 ]]; then + echo "::error::Found $CRITICAL_COUNT Critical and $HIGH_COUNT High severity vulnerabilities" + exit 1 + fi - name: "Check prerequisites for sending the reports" shell: bash id: check diff --git a/scripts/reports/parse-vulnerabilities.sh b/scripts/reports/parse-vulnerabilities.sh new file mode 100755 index 00000000..62320b51 --- /dev/null +++ b/scripts/reports/parse-vulnerabilities.sh @@ -0,0 +1,89 @@ +#!/bin/bash +# +# Parse vulnerability report JSON and output a human-readable summary +# Usage: ./parse-vulnerabilities.sh +# + +set -euo pipefail + +if [[ $# -lt 1 ]]; then + echo "Usage: $0 " + exit 1 +fi + +REPORT_FILE="$1" + +if [[ ! -f "$REPORT_FILE" ]]; then + echo "Error: File not found: $REPORT_FILE" + exit 1 +fi + +if ! command -v jq &> /dev/null; then + echo "Error: jq is required but not installed" + exit 1 +fi + +# Get counts by severity +echo "## Vulnerability Report Summary" +echo "" + +CRITICAL_COUNT=$(jq '[.matches[] | select(.vulnerability.severity == "Critical")] | length' "$REPORT_FILE") +HIGH_COUNT=$(jq '[.matches[] | select(.vulnerability.severity == "High")] | length' "$REPORT_FILE") +MEDIUM_COUNT=$(jq '[.matches[] | select(.vulnerability.severity == "Medium")] | length' "$REPORT_FILE") +LOW_COUNT=$(jq '[.matches[] | select(.vulnerability.severity == "Low")] | length' "$REPORT_FILE") +TOTAL=$((CRITICAL_COUNT + HIGH_COUNT + MEDIUM_COUNT + LOW_COUNT)) + +echo "**Total: $TOTAL vulnerabilities** ($CRITICAL_COUNT Critical, $HIGH_COUNT High, $MEDIUM_COUNT Medium, $LOW_COUNT Low)" +echo "" + +# Function to print vulnerabilities for a given severity +print_severity_section() { + local severity="$1" + local count="$2" + + if [[ "$count" -eq 0 ]]; then + return + fi + + echo "### $severity ($count)" + echo "" + echo "| Package | Version | Fix | Description |" + echo "|---------|---------|-----|-------------|" + + jq -r --arg sev "$severity" ' + [.matches[] | select(.vulnerability.severity == $sev) | { + id: .vulnerability.id, + severity: .vulnerability.severity, + package: .artifact.name, + version: .artifact.version, + fix: (.vulnerability.fix.versions[0] // "N/A"), + description: .vulnerability.description + }] + | unique_by(.id + .package + .version) + | sort_by(.package) + | .[] + | "| \(.package) | \(.version) | \(.fix) | \(.description[0:70])... |" + ' "$REPORT_FILE" + + echo "" +} + +print_severity_section "Critical" "$CRITICAL_COUNT" +print_severity_section "High" "$HIGH_COUNT" +print_severity_section "Medium" "$MEDIUM_COUNT" +print_severity_section "Low" "$LOW_COUNT" + +# Priority packages summary +echo "---" +echo "" +echo "### Priority Packages to Update" +echo "" + +jq -r ' + [.matches[] | select(.vulnerability.severity == "Critical" or .vulnerability.severity == "High") | .artifact.name] + | unique + | sort + | join(", ") +' "$REPORT_FILE" | fold -s -w 80 + +echo "" From 9446e105a6d5cac7724902d633bfad2cf3f1aff1 Mon Sep 17 00:00:00 2001 From: Tim Ireland Date: Fri, 22 May 2026 16:25:30 +0100 Subject: [PATCH 2/6] CCM-1725: updating action to fail on build and report package vulns show language Signed-off-by: Tim Ireland --- scripts/reports/parse-vulnerabilities.sh | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/reports/parse-vulnerabilities.sh b/scripts/reports/parse-vulnerabilities.sh index 62320b51..a73b105f 100755 --- a/scripts/reports/parse-vulnerabilities.sh +++ b/scripts/reports/parse-vulnerabilities.sh @@ -47,14 +47,15 @@ print_severity_section() { echo "### $severity ($count)" echo "" - echo "| Package | Version | Fix | Description |" - echo "|---------|---------|-----|-------------|" + echo "| Package | Language | Version | Fix | Description |" + echo "|---------|---------|---------|-----|-------------|" jq -r --arg sev "$severity" ' [.matches[] | select(.vulnerability.severity == $sev) | { id: .vulnerability.id, severity: .vulnerability.severity, package: .artifact.name, + language: .artifact.language, version: .artifact.version, fix: (.vulnerability.fix.versions[0] // "N/A"), description: .vulnerability.description @@ -62,7 +63,7 @@ print_severity_section() { | unique_by(.id + .package + .version) | sort_by(.package) | .[] - | "| \(.package) | \(.version) | \(.fix) | \(.description[0:70])... |" + | "| \(.package) | \(.language) | \(.version) | \(.fix) | \(.description[0:70])... |" ' "$REPORT_FILE" echo "" From 8f94a8474916bdbbfcb9df9c9504615caabd6327 Mon Sep 17 00:00:00 2001 From: Tim Ireland Date: Thu, 28 May 2026 15:45:21 +0100 Subject: [PATCH 3/6] Potential fix for pull request finding refactor script Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- scripts/reports/parse-vulnerabilities.sh | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/scripts/reports/parse-vulnerabilities.sh b/scripts/reports/parse-vulnerabilities.sh index a73b105f..d12d1a5c 100755 --- a/scripts/reports/parse-vulnerabilities.sh +++ b/scripts/reports/parse-vulnerabilities.sh @@ -24,13 +24,27 @@ if ! command -v jq &> /dev/null; then fi # Get counts by severity +count_unique_severity() { + local severity="$1" + + jq -r --arg sev "$severity" ' + [.matches[] | select(.vulnerability.severity == $sev) | { + id: .vulnerability.id, + package: .artifact.name, + version: .artifact.version + }] + | unique_by(.id + .package + .version) + | length + ' "$REPORT_FILE" +} + echo "## Vulnerability Report Summary" echo "" -CRITICAL_COUNT=$(jq '[.matches[] | select(.vulnerability.severity == "Critical")] | length' "$REPORT_FILE") -HIGH_COUNT=$(jq '[.matches[] | select(.vulnerability.severity == "High")] | length' "$REPORT_FILE") -MEDIUM_COUNT=$(jq '[.matches[] | select(.vulnerability.severity == "Medium")] | length' "$REPORT_FILE") -LOW_COUNT=$(jq '[.matches[] | select(.vulnerability.severity == "Low")] | length' "$REPORT_FILE") +CRITICAL_COUNT=$(count_unique_severity "Critical") +HIGH_COUNT=$(count_unique_severity "High") +MEDIUM_COUNT=$(count_unique_severity "Medium") +LOW_COUNT=$(count_unique_severity "Low") TOTAL=$((CRITICAL_COUNT + HIGH_COUNT + MEDIUM_COUNT + LOW_COUNT)) echo "**Total: $TOTAL vulnerabilities** ($CRITICAL_COUNT Critical, $HIGH_COUNT High, $MEDIUM_COUNT Medium, $LOW_COUNT Low)" From 43d64717acaa301060b20bea7d53e49d39532a15 Mon Sep 17 00:00:00 2001 From: Tim Ireland Date: Thu, 28 May 2026 15:52:57 +0100 Subject: [PATCH 4/6] Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .github/actions/scan-dependencies/action.yaml | 2 +- scripts/reports/parse-vulnerabilities.sh | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/actions/scan-dependencies/action.yaml b/.github/actions/scan-dependencies/action.yaml index 2b266bc3..76af32fc 100644 --- a/.github/actions/scan-dependencies/action.yaml +++ b/.github/actions/scan-dependencies/action.yaml @@ -61,7 +61,7 @@ runs: retention-days: 21 - name: "Upload vulnerabilities summary as an artefact" if: ${{ !env.ACT }} - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: vulnerabilities-summary.md path: ./vulnerabilities-summary.md diff --git a/scripts/reports/parse-vulnerabilities.sh b/scripts/reports/parse-vulnerabilities.sh index d12d1a5c..21f57020 100755 --- a/scripts/reports/parse-vulnerabilities.sh +++ b/scripts/reports/parse-vulnerabilities.sh @@ -1,5 +1,8 @@ #!/bin/bash # +# WARNING: This file is managed via the repository template. +# Local changes may diverge from the template source of truth. +# # Parse vulnerability report JSON and output a human-readable summary # Usage: ./parse-vulnerabilities.sh # From 853f65527ac264f00cb433e6efa267e088a62cfc Mon Sep 17 00:00:00 2001 From: Tim Ireland Date: Mon, 15 Jun 2026 16:49:38 +0100 Subject: [PATCH 5/6] CCM-17525: Added check to skip analysis Signed-off-by: Tim Ireland --- .github/actions/scan-dependencies/action.yaml | 51 ++++++++++++++++--- scripts/reports/create-sbom-report.sh | 11 ++-- scripts/reports/scan-vulnerabilities.sh | 11 ++-- 3 files changed, 59 insertions(+), 14 deletions(-) diff --git a/.github/actions/scan-dependencies/action.yaml b/.github/actions/scan-dependencies/action.yaml index 76af32fc..21ef10b4 100644 --- a/.github/actions/scan-dependencies/action.yaml +++ b/.github/actions/scan-dependencies/action.yaml @@ -19,54 +19,88 @@ inputs: idp_aws_report_upload_bucket_endpoint: description: "IDP AWS report upload endpoint to upload the report to" required: false + skip_if_pr_has_label: + description: "Skip dependency scanning when the triggering PR has this label" + required: false + default: "skip-dependencies-check" runs: using: "composite" steps: + - name: "Check if dependency scan should be skipped" + id: skip-check + shell: bash + run: | + SHOULD_SKIP="false" + + if [[ -n "${{ inputs.skip_if_pr_has_label }}" ]] && [[ -f "${GITHUB_EVENT_PATH}" ]]; then + PR_LABELS=$(jq -r '.pull_request.labels[]?.name' "${GITHUB_EVENT_PATH}") + if echo "${PR_LABELS}" | grep -Fxq "${{ inputs.skip_if_pr_has_label }}"; then + SHOULD_SKIP="true" + fi + fi + + echo "should_skip=${SHOULD_SKIP}" >> "$GITHUB_OUTPUT" + - name: "Skip dependency scan" + if: steps.skip-check.outputs.should_skip == 'true' + shell: bash + run: | + echo "Dependency scan skipped because PR has label '${{ inputs.skip_if_pr_has_label }}'." - name: "Generate SBOM" + if: steps.skip-check.outputs.should_skip != 'true' shell: bash run: | + ACTION_ROOT="$(cd "${GITHUB_ACTION_PATH}/../../.." && pwd)" + export TOOLING_ROOT="${ACTION_ROOT}" export BUILD_DATETIME=${{ inputs.build_datetime }} - ./scripts/reports/create-sbom-report.sh + "${ACTION_ROOT}/scripts/reports/create-sbom-report.sh" - name: "Compress SBOM report" + if: steps.skip-check.outputs.should_skip != 'true' shell: bash run: zip sbom-repository-report.json.zip sbom-repository-report.json - name: "Upload SBOM report as an artefact" - if: ${{ !env.ACT }} + if: ${{ !env.ACT && steps.skip-check.outputs.should_skip != 'true' }} uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: sbom-repository-report.json.zip path: ./sbom-repository-report.json.zip retention-days: 21 - name: "Scan vulnerabilities" + if: steps.skip-check.outputs.should_skip != 'true' shell: bash run: | + ACTION_ROOT="$(cd "${GITHUB_ACTION_PATH}/../../.." && pwd)" + export TOOLING_ROOT="${ACTION_ROOT}" export BUILD_DATETIME=${{ inputs.build_datetime }} - ./scripts/reports/scan-vulnerabilities.sh + "${ACTION_ROOT}/scripts/reports/scan-vulnerabilities.sh" - name: "Generate vulnerabilities summary" + if: steps.skip-check.outputs.should_skip != 'true' shell: bash run: | - ./scripts/reports/parse-vulnerabilities.sh vulnerabilities-repository-report.json | tee vulnerabilities-summary.md + ACTION_ROOT="$(cd "${GITHUB_ACTION_PATH}/../../.." && pwd)" + "${ACTION_ROOT}/scripts/reports/parse-vulnerabilities.sh" vulnerabilities-repository-report.json | tee vulnerabilities-summary.md if [[ -n "${GITHUB_STEP_SUMMARY:-}" ]]; then cat vulnerabilities-summary.md >> "$GITHUB_STEP_SUMMARY" fi - name: "Compress vulnerabilities report" + if: steps.skip-check.outputs.should_skip != 'true' shell: bash run: zip vulnerabilities-repository-report.json.zip vulnerabilities-repository-report.json - name: "Upload vulnerabilities report as an artefact" - if: ${{ !env.ACT }} + if: ${{ !env.ACT && steps.skip-check.outputs.should_skip != 'true' }} uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: vulnerabilities-repository-report.json.zip path: ./vulnerabilities-repository-report.json.zip retention-days: 21 - name: "Upload vulnerabilities summary as an artefact" - if: ${{ !env.ACT }} + if: ${{ !env.ACT && steps.skip-check.outputs.should_skip != 'true' }} uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: vulnerabilities-summary.md path: ./vulnerabilities-summary.md retention-days: 21 - name: "Fail if Critical or High vulnerabilities found" + if: steps.skip-check.outputs.should_skip != 'true' shell: bash run: | CRITICAL_COUNT=$(jq '[.matches[] | select(.vulnerability.severity == "Critical")] | length' vulnerabilities-repository-report.json) @@ -77,18 +111,19 @@ runs: exit 1 fi - name: "Check prerequisites for sending the reports" + if: steps.skip-check.outputs.should_skip != 'true' shell: bash id: check run: echo "secrets_exist=${{ inputs.idp_aws_report_upload_role_name != '' && inputs.idp_aws_report_upload_bucket_endpoint != '' }}" >> $GITHUB_OUTPUT - name: "Authenticate to send the reports" - if: steps.check.outputs.secrets_exist == 'true' + if: steps.skip-check.outputs.should_skip != 'true' && steps.check.outputs.secrets_exist == 'true' uses: aws-actions/configure-aws-credentials@ec61189d14ec14c8efccab744f656cffd0e33f37 # v6 with: role-to-assume: arn:aws:iam::${{ inputs.idp_aws_report_upload_account_id }}:role/${{ inputs.idp_aws_report_upload_role_name }} aws-region: ${{ inputs.idp_aws_report_upload_region }} - name: "Send the SBOM and vulnerabilities reports to the central location" shell: bash - if: steps.check.outputs.secrets_exist == 'true' + if: steps.skip-check.outputs.should_skip != 'true' && steps.check.outputs.secrets_exist == 'true' run: | aws s3 cp \ ./sbom-repository-report.json.zip \ diff --git a/scripts/reports/create-sbom-report.sh b/scripts/reports/create-sbom-report.sh index 1ed735a7..a237f9f7 100755 --- a/scripts/reports/create-sbom-report.sh +++ b/scripts/reports/create-sbom-report.sh @@ -4,6 +4,10 @@ set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +DEFAULT_TOOLING_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" +TOOLING_ROOT="${TOOLING_ROOT:-${DEFAULT_TOOLING_ROOT}}" + # Script to generate SBOM (Software Bill of Materials) for the repository # content and any artefact created by the CI/CD pipeline. This is a syft command # wrapper. It will run syft natively if it is installed, otherwise it will run @@ -39,22 +43,23 @@ function create-report() { function run-syft-natively() { syft packages dir:"$PWD" \ - --config "$PWD/scripts/config/syft.yaml" \ + --config "$TOOLING_ROOT/scripts/config/syft.yaml" \ --output spdx-json="$PWD/sbom-repository-report.tmp.json" } function run-syft-in-docker() { # shellcheck disable=SC1091 - source ./scripts/docker/docker.lib.sh + source "$TOOLING_ROOT/scripts/docker/docker.lib.sh" # shellcheck disable=SC2155 local image=$(name=ghcr.io/anchore/syft docker-get-image-version-and-pull) docker run --rm --platform linux/amd64 \ --volume "$PWD":/workdir \ + --volume "$TOOLING_ROOT":/tooling \ "$image" \ packages dir:/workdir \ - --config /workdir/scripts/config/syft.yaml \ + --config /tooling/scripts/config/syft.yaml \ --output spdx-json=/workdir/sbom-repository-report.tmp.json } diff --git a/scripts/reports/scan-vulnerabilities.sh b/scripts/reports/scan-vulnerabilities.sh index eb68d4b5..544a3f66 100755 --- a/scripts/reports/scan-vulnerabilities.sh +++ b/scripts/reports/scan-vulnerabilities.sh @@ -4,6 +4,10 @@ set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +DEFAULT_TOOLING_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" +TOOLING_ROOT="${TOOLING_ROOT:-${DEFAULT_TOOLING_ROOT}}" + # Script to scan an SBOM file for CVEs (Common Vulnerabilities and Exposures). # This is a grype command wrapper. It will run grype natively if it is # installed, otherwise it will run it in a Docker container. @@ -42,7 +46,7 @@ function run-grype-natively() { grype \ sbom:"$PWD/sbom-repository-report.json" \ - --config "$PWD/scripts/config/grype.yaml" \ + --config "$TOOLING_ROOT/scripts/config/grype.yaml" \ --output json \ --file "$PWD/vulnerabilities-repository-report.tmp.json" } @@ -50,16 +54,17 @@ function run-grype-natively() { function run-grype-in-docker() { # shellcheck disable=SC1091 - source ./scripts/docker/docker.lib.sh + source "$TOOLING_ROOT/scripts/docker/docker.lib.sh" # shellcheck disable=SC2155 local image=$(name=ghcr.io/anchore/grype docker-get-image-version-and-pull) docker run --rm --platform linux/amd64 \ --volume "$PWD":/workdir \ + --volume "$TOOLING_ROOT":/tooling \ --volume /tmp/grype/db:/.cache/grype/db \ "$image" \ sbom:/workdir/sbom-repository-report.json \ - --config /workdir/scripts/config/grype.yaml \ + --config /tooling/scripts/config/grype.yaml \ --output json \ --file /workdir/vulnerabilities-repository-report.tmp.json } From 220bbc7c12a11fe36681389fa9275df35e7bdc56 Mon Sep 17 00:00:00 2001 From: Tim Ireland Date: Mon, 15 Jun 2026 17:10:23 +0100 Subject: [PATCH 6/6] CCM-17525: commented out trivy job Signed-off-by: Tim Ireland --- .github/workflows/stage-1-commit.yaml | 30 +++++++++++++-------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/stage-1-commit.yaml b/.github/workflows/stage-1-commit.yaml index 24b51f36..88bb7c67 100644 --- a/.github/workflows/stage-1-commit.yaml +++ b/.github/workflows/stage-1-commit.yaml @@ -151,21 +151,21 @@ jobs: uses: asdf-vm/actions/setup@b7bcd026f18772e44fe1026d729e1611cc435d47 # v4 - name: "Lint Terraform" uses: ./.github/actions/lint-terraform - trivy: - name: "Trivy Scan" - runs-on: ubuntu-latest - timeout-minutes: 5 - needs: detect-terraform-changes - if: needs.detect-terraform-changes.outputs.terraform_changed == 'true' - steps: - - name: "Checkout code" - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - name: "Setup ASDF" - uses: asdf-vm/actions/setup@b7bcd026f18772e44fe1026d729e1611cc435d47 # v4 - - name: "Perform Setup" - uses: ./.github/actions/setup - - name: "Trivy Scan" - uses: ./.github/actions/trivy + # trivy: + # name: "Trivy Scan" + # runs-on: ubuntu-latest + # timeout-minutes: 5 + # needs: detect-terraform-changes + # if: needs.detect-terraform-changes.outputs.terraform_changed == 'true' + # steps: + # - name: "Checkout code" + # uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + # - name: "Setup ASDF" + # uses: asdf-vm/actions/setup@b7bcd026f18772e44fe1026d729e1611cc435d47 # v4 + # - name: "Perform Setup" + # uses: ./.github/actions/setup + # - name: "Trivy Scan" + # uses: ./.github/actions/trivy count-lines-of-code: name: "Count lines of code" runs-on: ubuntu-latest