From a1ae34dd47ce99c74972cad679f65083412496f2 Mon Sep 17 00:00:00 2001 From: Tom Martensen Date: Mon, 8 Dec 2025 16:28:44 +0100 Subject: [PATCH 01/29] ROX-30730: add action for iamge-vulnerability-check --- release/check-image-vulnerabilities/README.md | 124 ++++++++++++++++++ .../check-image-vulnerabilities/action.yml | 82 ++++++++++++ 2 files changed, 206 insertions(+) create mode 100644 release/check-image-vulnerabilities/README.md create mode 100644 release/check-image-vulnerabilities/action.yml diff --git a/release/check-image-vulnerabilities/README.md b/release/check-image-vulnerabilities/README.md new file mode 100644 index 0000000..cbadd12 --- /dev/null +++ b/release/check-image-vulnerabilities/README.md @@ -0,0 +1,124 @@ +# Check Image Vulnerabilities + +Scan a container image for vulnerabilities using `roxctl image scan` and fail if critical or important vulnerabilities are found. + +This action waits for an image to be available on Quay.io, scans it using roxctl, and generates a detailed vulnerability report in the GitHub step summary. + +## Required permissions + +```yaml +permissions: + # Needed for stackrox/central-login to create the JWT token. + id-token: write +``` + +## All options + +| Input | Description | Default | +| ----------------------------------------- | -------------------------------------------------- | --------- | +| [image](#image) | Image name (without registry prefix) | | +| [version](#version) | Image version tag | | +| [wait-limit](#wait-limit) | Maximum time to wait for image (seconds) | `"7200"` | +| [summary-title](#summary-title) | Title prefix for the GitHub step summary | | +| [quay-bearer-token](#quay-bearer-token) | Quay.io bearer token for wait-for-image | | +| [central-url](#central-url) | ACS Central URL | | + +## Outputs + +| Output | Description | +| ----------------------------------------- | ------------------------------------- | +| [scan-result-path](#scan-result-path) | Path to the scan result JSON file | + +### Detailed options + +#### image + +Image name without the registry prefix. The action will automatically prepend `quay.io/rhacs-eng/` to construct the full image reference. + +Example: `"main"` + +Default value: unset + +#### version + +Image version tag to scan. + +Example: `"3.76.1"` + +Default value: unset + +#### wait-limit + +Maximum time in seconds to wait for the image to be available on Quay.io before failing. + +Default value: `"7200"` (2 hours) + +#### summary-title + +Title prefix for the vulnerability report in the GitHub step summary. This helps identify which image the scan results correspond to when multiple scans are performed in a workflow. + +Example: `"Image Scan Results"` + +Default value: unset + +#### quay-bearer-token + +Bearer token for authenticating with the Quay.io API. This is required by the wait-for-image action to check if the image is available. + +Default value: unset + +#### central-url + +URL of the ACS/RHACS Central instance to use for scanning. + +Example: `"https://central.example.com"` + +Default value: unset + +### Detailed outputs + +#### scan-result-path + +Path to the JSON file containing the complete scan results from roxctl. This file can be used for further processing or artifact storage. + +Example: `"scan-result.json"` + +## Usage + +The action requires credentials for ACS Central to be available. It integrates with the `stackrox/central-login@v1` action which uses OIDC authentication. + +```yaml +name: Scan image for vulnerabilities + +jobs: + scan: + runs-on: ubuntu-latest + permissions: + id-token: write # Required for ACS Central OIDC login + steps: + - uses: stackrox/actions/release/check-image-vulnerabilities@v1 + with: + image: main + version: 3.76.1 + summary-title: "Main Image Scan" + quay-bearer-token: ${{ secrets.QUAY_BEARER_TOKEN }} + central-url: https://central.example.com +``` + +## Behavior + +The action performs the following steps: + +1. **Wait for image**: Waits for `quay.io/rhacs-eng/$IMAGE:$VERSION` to be available on Quay.io +2. **Login to Central**: Authenticates with ACS Central using OIDC +3. **Install roxctl**: Installs the roxctl CLI tool +4. **Scan image**: Scans the image for vulnerabilities +5. **Check results**: Fails the workflow if any CRITICAL or IMPORTANT vulnerabilities are found +6. **Generate report**: Outputs a formatted table of vulnerabilities to the GitHub step summary + +If vulnerabilities are found, the step summary will include: + +- Component name and version +- CVE ID and severity +- Fixed version (if available) +- Link to CVE information diff --git a/release/check-image-vulnerabilities/action.yml b/release/check-image-vulnerabilities/action.yml new file mode 100644 index 0000000..f6f9667 --- /dev/null +++ b/release/check-image-vulnerabilities/action.yml @@ -0,0 +1,82 @@ +name: "Check Image Vulnerabilities" +description: "Scan an image for vulnerabilities and fail if critical or important ones are found" + +inputs: + image: + description: "Image name (without registry prefix)" + required: true + version: + description: "Image version tag" + required: true + wait-limit: + description: "Maximum time to wait for image (seconds)" + required: false + default: "7200" + summary-title: + description: "Title prefix for the GitHub step summary" + required: true + quay-bearer-token: + description: "Quay.io bearer token for wait-for-image" + required: true + central-url: + description: "ACS Central URL" + required: true + +outputs: + scan-result-path: + description: "Path to the scan result JSON file" + value: ${{ steps.scan.outputs.result-path }} + +runs: + using: "composite" + steps: + - name: "Wait for image to be built: quay.io/rhacs-eng/${{ inputs.image }}:${{ inputs.version }}" + uses: stackrox/actions/release/wait-for-image@v1 + with: + token: ${{ inputs.quay-bearer-token }} + image: rhacs-eng/${{ inputs.image }}:${{ inputs.version }} + limit: ${{ inputs.wait-limit }} + + - name: Central login + uses: stackrox/central-login@v1 + with: + endpoint: ${{ inputs.central-url }} + + - name: Install roxctl + uses: stackrox/roxctl-installer-action@v1 + with: + central-endpoint: ${{ inputs.central-url }} + central-token: ${{ env.ROX_API_TOKEN }} + + - name: Scan image and check for vulnerabilities + id: scan + shell: bash + run: | + RESULT=$(roxctl image scan --output=json --force \ + --image="quay.io/rhacs-eng/${{ inputs.image }}:${{ inputs.version }}") + echo "$RESULT" > scan-result.json + echo "result-path=scan-result.json" >> "$GITHUB_OUTPUT" + CRITICAL_CNT=$(jq ".result.summary.CRITICAL" <<< "$RESULT") + IMPORTANT_CNT=$(jq ".result.summary.IMPORTANT" <<< "$RESULT") + if (( CRITICAL_CNT + IMPORTANT_CNT > 0 )); then + echo "Found $CRITICAL_CNT critical vulnerabilities." + echo "Found $IMPORTANT_CNT important vulnerabilities." + echo "Check the workflow summary for a detailed list of the vulnerabilities." + { + echo "### ${{ inputs.summary-title }}: ${{ inputs.image }}:${{ inputs.version }}" + echo "" + # We need a monospaced font for the table layout, and `terraform` has + # nicer color highlighting than the default language agnostic code block. + echo '```terraform' + jq -r ' + .result.vulnerabilities // [] + | map(select(.cveSeverity == "CRITICAL" or .cveSeverity == "IMPORTANT")) + | (["COMPONENT","VERSION","CVE","SEVERITY","FIXED_VERSION","LINK"] | @tsv), + (.[] | [.componentName // "", .componentVersion // "", .cveId // "", .cveSeverity // "", .componentFixedVersion // "", .cveInfo // ""] | @tsv) + ' <<< "$RESULT" | column -t -s $'\t' + echo '```' + } >> "$GITHUB_STEP_SUMMARY" + exit 1 + else + echo "No important or critical vulnerabilities found." + fi From ffefc4f61a0b347ca1e436b84cc1677aa8c62a53 Mon Sep 17 00:00:00 2001 From: Tom Martensen Date: Mon, 8 Dec 2025 16:44:37 +0100 Subject: [PATCH 02/29] remove superfluous step ID --- release/check-image-vulnerabilities/action.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/release/check-image-vulnerabilities/action.yml b/release/check-image-vulnerabilities/action.yml index f6f9667..f08125a 100644 --- a/release/check-image-vulnerabilities/action.yml +++ b/release/check-image-vulnerabilities/action.yml @@ -49,7 +49,6 @@ runs: central-token: ${{ env.ROX_API_TOKEN }} - name: Scan image and check for vulnerabilities - id: scan shell: bash run: | RESULT=$(roxctl image scan --output=json --force \ From 61ef92901d2165b68e943c90cddb70ae31dd283b Mon Sep 17 00:00:00 2001 From: Tom Martensen Date: Tue, 9 Dec 2025 09:39:02 +0100 Subject: [PATCH 03/29] Revert "remove superfluous step ID" This reverts commit 14030cb1cb28b04116035191549ced1433ecf15a. --- release/check-image-vulnerabilities/action.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/release/check-image-vulnerabilities/action.yml b/release/check-image-vulnerabilities/action.yml index f08125a..f6f9667 100644 --- a/release/check-image-vulnerabilities/action.yml +++ b/release/check-image-vulnerabilities/action.yml @@ -49,6 +49,7 @@ runs: central-token: ${{ env.ROX_API_TOKEN }} - name: Scan image and check for vulnerabilities + id: scan shell: bash run: | RESULT=$(roxctl image scan --output=json --force \ From 6bb87b82f0997e00512556713bf08c744a4a2943 Mon Sep 17 00:00:00 2001 From: Tom Martensen Date: Tue, 9 Dec 2025 09:48:39 +0100 Subject: [PATCH 04/29] prepend only quay.io, require repository to be the passed in parameter --- release/check-image-vulnerabilities/README.md | 6 +++--- release/check-image-vulnerabilities/action.yml | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/release/check-image-vulnerabilities/README.md b/release/check-image-vulnerabilities/README.md index cbadd12..9b74a8d 100644 --- a/release/check-image-vulnerabilities/README.md +++ b/release/check-image-vulnerabilities/README.md @@ -33,7 +33,7 @@ permissions: #### image -Image name without the registry prefix. The action will automatically prepend `quay.io/rhacs-eng/` to construct the full image reference. +Image name without the registry prefix. The action will automatically prepend `quay.io/` to construct the full image reference. Example: `"main"` @@ -98,7 +98,7 @@ jobs: steps: - uses: stackrox/actions/release/check-image-vulnerabilities@v1 with: - image: main + image: rhacs-eng/main version: 3.76.1 summary-title: "Main Image Scan" quay-bearer-token: ${{ secrets.QUAY_BEARER_TOKEN }} @@ -109,7 +109,7 @@ jobs: The action performs the following steps: -1. **Wait for image**: Waits for `quay.io/rhacs-eng/$IMAGE:$VERSION` to be available on Quay.io +1. **Wait for image**: Waits for `quay.io/$IMAGE:$VERSION` to be available on Quay.io 2. **Login to Central**: Authenticates with ACS Central using OIDC 3. **Install roxctl**: Installs the roxctl CLI tool 4. **Scan image**: Scans the image for vulnerabilities diff --git a/release/check-image-vulnerabilities/action.yml b/release/check-image-vulnerabilities/action.yml index f6f9667..c5c9ef4 100644 --- a/release/check-image-vulnerabilities/action.yml +++ b/release/check-image-vulnerabilities/action.yml @@ -30,11 +30,11 @@ outputs: runs: using: "composite" steps: - - name: "Wait for image to be built: quay.io/rhacs-eng/${{ inputs.image }}:${{ inputs.version }}" + - name: "Wait for image to be built: quay.io/${{ inputs.image }}:${{ inputs.version }}" uses: stackrox/actions/release/wait-for-image@v1 with: token: ${{ inputs.quay-bearer-token }} - image: rhacs-eng/${{ inputs.image }}:${{ inputs.version }} + image: ${{ inputs.image }}:${{ inputs.version }} limit: ${{ inputs.wait-limit }} - name: Central login From 9bbec44560b5ef6cef1e7fcca608ee108cc99447 Mon Sep 17 00:00:00 2001 From: Tom Martensen Date: Tue, 9 Dec 2025 09:49:51 +0100 Subject: [PATCH 05/29] quote bash command --- release/check-image-vulnerabilities/action.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/release/check-image-vulnerabilities/action.yml b/release/check-image-vulnerabilities/action.yml index c5c9ef4..ad56440 100644 --- a/release/check-image-vulnerabilities/action.yml +++ b/release/check-image-vulnerabilities/action.yml @@ -52,8 +52,8 @@ runs: id: scan shell: bash run: | - RESULT=$(roxctl image scan --output=json --force \ - --image="quay.io/rhacs-eng/${{ inputs.image }}:${{ inputs.version }}") + RESULT="$(roxctl image scan --output=json --force \ + --image="quay.io/${{ inputs.image }}:${{ inputs.version }}")" echo "$RESULT" > scan-result.json echo "result-path=scan-result.json" >> "$GITHUB_OUTPUT" CRITICAL_CNT=$(jq ".result.summary.CRITICAL" <<< "$RESULT") From 7d112923c8815de3c72dd1b7bd96b45e771bed0b Mon Sep 17 00:00:00 2001 From: Tom Martensen Date: Tue, 9 Dec 2025 11:43:15 +0100 Subject: [PATCH 06/29] refactor: markdown table, only fail if fixable CVEs --- .../check-image-vulnerabilities/action.yml | 43 ++------- .../check-image-vulnerabilities.sh | 88 +++++++++++++++++++ 2 files changed, 95 insertions(+), 36 deletions(-) create mode 100644 release/check-image-vulnerabilities/check-image-vulnerabilities.sh diff --git a/release/check-image-vulnerabilities/action.yml b/release/check-image-vulnerabilities/action.yml index ad56440..b29d575 100644 --- a/release/check-image-vulnerabilities/action.yml +++ b/release/check-image-vulnerabilities/action.yml @@ -1,5 +1,5 @@ name: "Check Image Vulnerabilities" -description: "Scan an image for vulnerabilities and fail if critical or important ones are found" +description: "Scan an image for vulnerabilities and fail if fixablecritical or important ones are found" inputs: image: @@ -22,13 +22,8 @@ inputs: description: "ACS Central URL" required: true -outputs: - scan-result-path: - description: "Path to the scan result JSON file" - value: ${{ steps.scan.outputs.result-path }} - runs: - using: "composite" + using: composite steps: - name: "Wait for image to be built: quay.io/${{ inputs.image }}:${{ inputs.version }}" uses: stackrox/actions/release/wait-for-image@v1 @@ -49,34 +44,10 @@ runs: central-token: ${{ env.ROX_API_TOKEN }} - name: Scan image and check for vulnerabilities - id: scan shell: bash run: | - RESULT="$(roxctl image scan --output=json --force \ - --image="quay.io/${{ inputs.image }}:${{ inputs.version }}")" - echo "$RESULT" > scan-result.json - echo "result-path=scan-result.json" >> "$GITHUB_OUTPUT" - CRITICAL_CNT=$(jq ".result.summary.CRITICAL" <<< "$RESULT") - IMPORTANT_CNT=$(jq ".result.summary.IMPORTANT" <<< "$RESULT") - if (( CRITICAL_CNT + IMPORTANT_CNT > 0 )); then - echo "Found $CRITICAL_CNT critical vulnerabilities." - echo "Found $IMPORTANT_CNT important vulnerabilities." - echo "Check the workflow summary for a detailed list of the vulnerabilities." - { - echo "### ${{ inputs.summary-title }}: ${{ inputs.image }}:${{ inputs.version }}" - echo "" - # We need a monospaced font for the table layout, and `terraform` has - # nicer color highlighting than the default language agnostic code block. - echo '```terraform' - jq -r ' - .result.vulnerabilities // [] - | map(select(.cveSeverity == "CRITICAL" or .cveSeverity == "IMPORTANT")) - | (["COMPONENT","VERSION","CVE","SEVERITY","FIXED_VERSION","LINK"] | @tsv), - (.[] | [.componentName // "", .componentVersion // "", .cveId // "", .cveSeverity // "", .componentFixedVersion // "", .cveInfo // ""] | @tsv) - ' <<< "$RESULT" | column -t -s $'\t' - echo '```' - } >> "$GITHUB_STEP_SUMMARY" - exit 1 - else - echo "No important or critical vulnerabilities found." - fi + set -uo pipefail + "${{ github.action_path }}/../../common/common.sh" \ + "${{ github.action_path }}/check-image-vulnerabilities.sh" \ + "${{ inputs.image }}" \ + "${{ inputs.version }}" diff --git a/release/check-image-vulnerabilities/check-image-vulnerabilities.sh b/release/check-image-vulnerabilities/check-image-vulnerabilities.sh new file mode 100644 index 0000000..f25d659 --- /dev/null +++ b/release/check-image-vulnerabilities/check-image-vulnerabilities.sh @@ -0,0 +1,88 @@ +#!/usr/bin/env bash +# +# Counts the number of fixable vulnerabilities in the scan result. +# +# Local run: +# +# test/local-env.sh release/check-image-vulnerabilities/check-image-vulnerabilities.sh +# +set -euo pipefail + +IMAGE="${1:-}" +VERSION="${2:-}" + +check_not_empty \ + IMAGE \ + VERSION + +function count_fixable_vulnerabilities() { + local severity="$1" + local result_path="$2" + jq "[.result.vulnerabilities[] | select(.cveSeverity == \"$severity\" and .componentFixedVersion != \"\")] | length" "$result_path" +} + +function count_vulnerabilities() { + local severity="$1" + local result_path="$2" + jq ".result.summary.$severity" "$result_path" +} + +function scan_image() { + local image="$1" + local version="$2" + local result_path="$3" + roxctl image scan --output=json --force \ + --severity="MODERATE,IMPORTANT,CRITICAL" \ + --image="quay.io/${image}:${version}" > "$result_path" + gh_output result-path "$result_path" +} + +# Prints a markdown table of the vulnerabilities, sorted by severity. +function print_table() { + local result_path="$1" + jq -r ' + .result.vulnerabilities // [] + | (["COMPONENT","VERSION","CVE","SEVERITY","FIXED_VERSION","LINK"] | @csv), + (["---","---","---","---","---","---"] | @csv), + (.[] | [.componentName // "", .componentVersion // "", .cveId // "", .cveSeverity // "", .componentFixedVersion // "", .cveInfo // ""] | @csv) + ' <(cat "$result_path") \ + | sed 's/,/ | /g' \ + | sed 's/^/| /' \ + | sed 's/$/ |/' \ + | sed 's/"//g' +} + +function print_summary_message() { + local severity="$1" + local cnt="$2" + local fixable_cnt="$3" + gh_summary "* Found $cnt $severity vulnerabilities, of which $fixable_cnt are fixable." +} + +result_path="scan-result.json" +scan_image "$IMAGE" "$VERSION" "$result_path" + +CRITICAL_CNT=$(count_vulnerabilities "CRITICAL" "$result_path") +CRITICAL_FIXABLE_CNT=$(count_fixable_vulnerabilities "CRITICAL" "$result_path") + +IMPORTANT_CNT=$(count_vulnerabilities "IMPORTANT" "$result_path") +IMPORTANT_FIXABLE_CNT=$(count_fixable_vulnerabilities "IMPORTANT" "$result_path") + +MODERATE_CNT=$(count_vulnerabilities "MODERATE" "$result_path") +MODERATE_FIXABLE_CNT=$(count_fixable_vulnerabilities "MODERATE" "$result_path") + +gh_summary "### $IMAGE:$VERSION" +print_summary_message "CRITICAL" "$CRITICAL_CNT" "$CRITICAL_FIXABLE_CNT" +print_summary_message "IMPORTANT" "$IMPORTANT_CNT" "$IMPORTANT_FIXABLE_CNT" +print_summary_message "MODERATE" "$MODERATE_CNT" "$MODERATE_FIXABLE_CNT" + +# Print the vulnerabilities table in a collapsible section. +# For the table to render correctly, we need to add a newline after the summary. +gh_summary "
Vulnerabilities\n" +gh_summary "$(print_table "$result_path")" +gh_summary "
" + +if [[ "$CRITICAL_FIXABLE_CNT" -gt 0 || "$IMPORTANT_FIXABLE_CNT" -gt 0 ]]; then + gh_log "error" "Found fixable critical or important vulnerabilities. See the step summary for details." + exit 1 +fi From bbbfed22ef51a1cd372a25dfc8f8a4abcd3c7a3b Mon Sep 17 00:00:00 2001 From: Tom Martensen Date: Tue, 9 Dec 2025 11:48:06 +0100 Subject: [PATCH 07/29] make script executable --- .../check-image-vulnerabilities/check-image-vulnerabilities.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 release/check-image-vulnerabilities/check-image-vulnerabilities.sh diff --git a/release/check-image-vulnerabilities/check-image-vulnerabilities.sh b/release/check-image-vulnerabilities/check-image-vulnerabilities.sh old mode 100644 new mode 100755 From 904332da91bb948514122ad36c50c997f96c3a87 Mon Sep 17 00:00:00 2001 From: Tom Martensen Date: Tue, 9 Dec 2025 12:06:40 +0100 Subject: [PATCH 08/29] cleanups --- .../check-image-vulnerabilities.sh | 41 +++++++++++++------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/release/check-image-vulnerabilities/check-image-vulnerabilities.sh b/release/check-image-vulnerabilities/check-image-vulnerabilities.sh index f25d659..e18d2b7 100755 --- a/release/check-image-vulnerabilities/check-image-vulnerabilities.sh +++ b/release/check-image-vulnerabilities/check-image-vulnerabilities.sh @@ -52,6 +52,20 @@ function print_table() { | sed 's/"//g' } +function print_vulnerability_status() { + local -n vuln_counts_ref=$1 + local -n fixable_counts_ref=$2 + + for severity in CRITICAL IMPORTANT MODERATE; do + print_summary_message "$severity" "${vuln_counts_ref[$severity]}" "${fixable_counts_ref[$severity]}" + done + + if (( fixable_counts_ref[CRITICAL] > 0 || fixable_counts_ref[IMPORTANT] > 0 )); then + gh_log "error" "Found fixable critical or important vulnerabilities. See the step summary for details." + touch failure_flag + fi +} + function print_summary_message() { local severity="$1" local cnt="$2" @@ -62,19 +76,23 @@ function print_summary_message() { result_path="scan-result.json" scan_image "$IMAGE" "$VERSION" "$result_path" -CRITICAL_CNT=$(count_vulnerabilities "CRITICAL" "$result_path") -CRITICAL_FIXABLE_CNT=$(count_fixable_vulnerabilities "CRITICAL" "$result_path") - -IMPORTANT_CNT=$(count_vulnerabilities "IMPORTANT" "$result_path") -IMPORTANT_FIXABLE_CNT=$(count_fixable_vulnerabilities "IMPORTANT" "$result_path") +# Count the number of vulnerabilities and fixable vulnerabilities for each severity. +# Use associative arrays to store counts by severity. +# Arrays are used via nameref parameters in print_vulnerability_status function. +# shellcheck disable=SC2034 +declare -A vuln_counts +# shellcheck disable=SC2034 +declare -A fixable_counts -MODERATE_CNT=$(count_vulnerabilities "MODERATE" "$result_path") -MODERATE_FIXABLE_CNT=$(count_fixable_vulnerabilities "MODERATE" "$result_path") +# shellcheck disable=SC2034 +for severity in CRITICAL IMPORTANT MODERATE; do + vuln_counts[$severity]=$(count_vulnerabilities "$severity" "$result_path") + fixable_counts[$severity]=$(count_fixable_vulnerabilities "$severity" "$result_path") +done +# Print the summary of the vulnerabilities. gh_summary "### $IMAGE:$VERSION" -print_summary_message "CRITICAL" "$CRITICAL_CNT" "$CRITICAL_FIXABLE_CNT" -print_summary_message "IMPORTANT" "$IMPORTANT_CNT" "$IMPORTANT_FIXABLE_CNT" -print_summary_message "MODERATE" "$MODERATE_CNT" "$MODERATE_FIXABLE_CNT" +print_vulnerability_status vuln_counts fixable_counts # Print the vulnerabilities table in a collapsible section. # For the table to render correctly, we need to add a newline after the summary. @@ -82,7 +100,6 @@ gh_summary "
Vulnerabilities\n" gh_summary "$(print_table "$result_path")" gh_summary "
" -if [[ "$CRITICAL_FIXABLE_CNT" -gt 0 || "$IMPORTANT_FIXABLE_CNT" -gt 0 ]]; then - gh_log "error" "Found fixable critical or important vulnerabilities. See the step summary for details." +if [[ -f failure_flag ]]; then exit 1 fi From 48015ea437f819cca58a9bd50a794410f5fc87c6 Mon Sep 17 00:00:00 2001 From: Tom Martensen Date: Tue, 9 Dec 2025 12:08:55 +0100 Subject: [PATCH 09/29] update documentation --- release/check-image-vulnerabilities/README.md | 22 ++++--------------- 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/release/check-image-vulnerabilities/README.md b/release/check-image-vulnerabilities/README.md index 9b74a8d..988f91f 100644 --- a/release/check-image-vulnerabilities/README.md +++ b/release/check-image-vulnerabilities/README.md @@ -1,8 +1,8 @@ # Check Image Vulnerabilities -Scan a container image for vulnerabilities using `roxctl image scan` and fail if critical or important vulnerabilities are found. +Scan a container image on quay.io for vulnerabilities using `roxctl image scan` and fail if fixable critical or important vulnerabilities are found. -This action waits for an image to be available on Quay.io, scans it using roxctl, and generates a detailed vulnerability report in the GitHub step summary. +This action waits for an image to be available on Quay.io, scans it using roxctl, and generates a detailed vulnerability report in the GitHub step summary, while making the raw report available as JSON in the workspace as `scan-result.json`. ## Required permissions @@ -23,12 +23,6 @@ permissions: | [quay-bearer-token](#quay-bearer-token) | Quay.io bearer token for wait-for-image | | | [central-url](#central-url) | ACS Central URL | | -## Outputs - -| Output | Description | -| ----------------------------------------- | ------------------------------------- | -| [scan-result-path](#scan-result-path) | Path to the scan result JSON file | - ### Detailed options #### image @@ -75,14 +69,6 @@ Example: `"https://central.example.com"` Default value: unset -### Detailed outputs - -#### scan-result-path - -Path to the JSON file containing the complete scan results from roxctl. This file can be used for further processing or artifact storage. - -Example: `"scan-result.json"` - ## Usage The action requires credentials for ACS Central to be available. It integrates with the `stackrox/central-login@v1` action which uses OIDC authentication. @@ -113,8 +99,8 @@ The action performs the following steps: 2. **Login to Central**: Authenticates with ACS Central using OIDC 3. **Install roxctl**: Installs the roxctl CLI tool 4. **Scan image**: Scans the image for vulnerabilities -5. **Check results**: Fails the workflow if any CRITICAL or IMPORTANT vulnerabilities are found -6. **Generate report**: Outputs a formatted table of vulnerabilities to the GitHub step summary +5. **Generate report**: Outputs a formatted table of vulnerabilities to the GitHub step summary +6. **Check results**: Fails the workflow if any CRITICAL or IMPORTANT vulnerabilities are found If vulnerabilities are found, the step summary will include: From c9fe987afdf756fd6f931eaa08f11e40bffbded4 Mon Sep 17 00:00:00 2001 From: Tom Martensen Date: Tue, 9 Dec 2025 12:19:13 +0100 Subject: [PATCH 10/29] update summary --- .../check-image-vulnerabilities.sh | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/release/check-image-vulnerabilities/check-image-vulnerabilities.sh b/release/check-image-vulnerabilities/check-image-vulnerabilities.sh index e18d2b7..52442f7 100755 --- a/release/check-image-vulnerabilities/check-image-vulnerabilities.sh +++ b/release/check-image-vulnerabilities/check-image-vulnerabilities.sh @@ -56,21 +56,20 @@ function print_vulnerability_status() { local -n vuln_counts_ref=$1 local -n fixable_counts_ref=$2 - for severity in CRITICAL IMPORTANT MODERATE; do - print_summary_message "$severity" "${vuln_counts_ref[$severity]}" "${fixable_counts_ref[$severity]}" - done - if (( fixable_counts_ref[CRITICAL] > 0 || fixable_counts_ref[IMPORTANT] > 0 )); then gh_log "error" "Found fixable critical or important vulnerabilities. See the step summary for details." touch failure_flag + gh_summary "Status: ❌" + else + gh_summary "Status: ✅" fi -} -function print_summary_message() { - local severity="$1" - local cnt="$2" - local fixable_cnt="$3" - gh_summary "* Found $cnt $severity vulnerabilities, of which $fixable_cnt are fixable." + gh_summary "" + gh_summary "| Severity | Total | Fixable |" + gh_summary "| --- | --- | --- |" + for severity in CRITICAL IMPORTANT MODERATE; do + gh_summary "| $severity | ${vuln_counts_ref[$severity]} | ${fixable_counts_ref[$severity]} |" + done } result_path="scan-result.json" @@ -96,7 +95,7 @@ print_vulnerability_status vuln_counts fixable_counts # Print the vulnerabilities table in a collapsible section. # For the table to render correctly, we need to add a newline after the summary. -gh_summary "
Vulnerabilities\n" +gh_summary "
Click to expand details\n" gh_summary "$(print_table "$result_path")" gh_summary "
" From 85a652f12cdce8a1148ae77416ac00bad7e320c3 Mon Sep 17 00:00:00 2001 From: Tom Martensen Date: Tue, 9 Dec 2025 12:21:43 +0100 Subject: [PATCH 11/29] add title prefix --- release/check-image-vulnerabilities/action.yml | 5 +++-- .../check-image-vulnerabilities.sh | 6 ++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/release/check-image-vulnerabilities/action.yml b/release/check-image-vulnerabilities/action.yml index b29d575..7124ecb 100644 --- a/release/check-image-vulnerabilities/action.yml +++ b/release/check-image-vulnerabilities/action.yml @@ -12,7 +12,7 @@ inputs: description: "Maximum time to wait for image (seconds)" required: false default: "7200" - summary-title: + summary-prefix: description: "Title prefix for the GitHub step summary" required: true quay-bearer-token: @@ -50,4 +50,5 @@ runs: "${{ github.action_path }}/../../common/common.sh" \ "${{ github.action_path }}/check-image-vulnerabilities.sh" \ "${{ inputs.image }}" \ - "${{ inputs.version }}" + "${{ inputs.version }}" \ + "${{ inputs.summary-prefix }}" diff --git a/release/check-image-vulnerabilities/check-image-vulnerabilities.sh b/release/check-image-vulnerabilities/check-image-vulnerabilities.sh index 52442f7..cd16902 100755 --- a/release/check-image-vulnerabilities/check-image-vulnerabilities.sh +++ b/release/check-image-vulnerabilities/check-image-vulnerabilities.sh @@ -10,10 +10,12 @@ set -euo pipefail IMAGE="${1:-}" VERSION="${2:-}" +SUMMARY_PREFIX="${3:-}" check_not_empty \ IMAGE \ - VERSION + VERSION \ + SUMMARY_PREFIX function count_fixable_vulnerabilities() { local severity="$1" @@ -90,7 +92,7 @@ for severity in CRITICAL IMPORTANT MODERATE; do done # Print the summary of the vulnerabilities. -gh_summary "### $IMAGE:$VERSION" +gh_summary "### $SUMMARY_PREFIX: $IMAGE:$VERSION" print_vulnerability_status vuln_counts fixable_counts # Print the vulnerabilities table in a collapsible section. From 4c9379e7f29c0f98b863821ccaaf8bbc8bc7e9f6 Mon Sep 17 00:00:00 2001 From: Tom Martensen Date: Tue, 9 Dec 2025 12:24:27 +0100 Subject: [PATCH 12/29] fix typo --- release/check-image-vulnerabilities/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release/check-image-vulnerabilities/action.yml b/release/check-image-vulnerabilities/action.yml index 7124ecb..9d6b95e 100644 --- a/release/check-image-vulnerabilities/action.yml +++ b/release/check-image-vulnerabilities/action.yml @@ -1,5 +1,5 @@ name: "Check Image Vulnerabilities" -description: "Scan an image for vulnerabilities and fail if fixablecritical or important ones are found" +description: "Scan an image for vulnerabilities and fail if fixable critical or important ones are found" inputs: image: From d6f9927358839e8e8002c72c9f15d87c8c8de42d Mon Sep 17 00:00:00 2001 From: Tom Martensen Date: Tue, 9 Dec 2025 12:58:44 +0100 Subject: [PATCH 13/29] fix formatting --- .../check-image-vulnerabilities/check-image-vulnerabilities.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release/check-image-vulnerabilities/check-image-vulnerabilities.sh b/release/check-image-vulnerabilities/check-image-vulnerabilities.sh index cd16902..9b5ebc5 100755 --- a/release/check-image-vulnerabilities/check-image-vulnerabilities.sh +++ b/release/check-image-vulnerabilities/check-image-vulnerabilities.sh @@ -92,7 +92,7 @@ for severity in CRITICAL IMPORTANT MODERATE; do done # Print the summary of the vulnerabilities. -gh_summary "### $SUMMARY_PREFIX: $IMAGE:$VERSION" +gh_summary "### $SUMMARY_PREFIX $IMAGE:$VERSION" print_vulnerability_status vuln_counts fixable_counts # Print the vulnerabilities table in a collapsible section. From 7025a2a3b678d3e74869f723273a9a5615289203 Mon Sep 17 00:00:00 2001 From: Tom Martensen Date: Tue, 9 Dec 2025 13:12:50 +0100 Subject: [PATCH 14/29] handle no vulnerabilities found --- .../check-image-vulnerabilities.sh | 117 ++++++++++-------- 1 file changed, 65 insertions(+), 52 deletions(-) diff --git a/release/check-image-vulnerabilities/check-image-vulnerabilities.sh b/release/check-image-vulnerabilities/check-image-vulnerabilities.sh index 9b5ebc5..f27e8d3 100755 --- a/release/check-image-vulnerabilities/check-image-vulnerabilities.sh +++ b/release/check-image-vulnerabilities/check-image-vulnerabilities.sh @@ -17,18 +17,45 @@ check_not_empty \ VERSION \ SUMMARY_PREFIX -function count_fixable_vulnerabilities() { - local severity="$1" - local result_path="$2" - jq "[.result.vulnerabilities[] | select(.cveSeverity == \"$severity\" and .componentFixedVersion != \"\")] | length" "$result_path" -} +function main() { + local image="$1" + local version="$2" + local summary_prefix="$3" + local result_path="scan-result.json" -function count_vulnerabilities() { - local severity="$1" - local result_path="$2" - jq ".result.summary.$severity" "$result_path" + # Scan the image for vulnerabilities. + scan_image "$image" "$version" "$result_path" + + # Count the number of vulnerabilities and fixable vulnerabilities for each severity. + # Use associative arrays to store counts by severity. + # Arrays are used via nameref parameters in print_vulnerability_status function. + # shellcheck disable=SC2034 + declare -A vuln_counts + # shellcheck disable=SC2034 + declare -A fixable_counts + + # shellcheck disable=SC2034 + for severity in CRITICAL IMPORTANT MODERATE; do + vuln_counts[$severity]=$(count_vulnerabilities "$severity" "$result_path") + fixable_counts[$severity]=$(count_fixable_vulnerabilities "$severity" "$result_path") + done + + # Print the summary of the vulnerabilities. + gh_summary "### $summary_prefix $image:$version" + print_vulnerability_status vuln_counts fixable_counts + + # Print the vulnerabilities table in a collapsible section. + # For the table to render correctly, we need to add a newline after the summary. + gh_summary "
Click to expand details\n" + gh_summary "$(print_table "$result_path")" + gh_summary "
" + + if [[ -f failure_flag ]]; then + exit 1 + fi } +# Scans the image for vulnerabilities. function scan_image() { local image="$1" local version="$2" @@ -39,21 +66,21 @@ function scan_image() { gh_output result-path "$result_path" } -# Prints a markdown table of the vulnerabilities, sorted by severity. -function print_table() { - local result_path="$1" - jq -r ' - .result.vulnerabilities // [] - | (["COMPONENT","VERSION","CVE","SEVERITY","FIXED_VERSION","LINK"] | @csv), - (["---","---","---","---","---","---"] | @csv), - (.[] | [.componentName // "", .componentVersion // "", .cveId // "", .cveSeverity // "", .componentFixedVersion // "", .cveInfo // ""] | @csv) - ' <(cat "$result_path") \ - | sed 's/,/ | /g' \ - | sed 's/^/| /' \ - | sed 's/$/ |/' \ - | sed 's/"//g' +# Counts the number of vulnerabilities for a given severity. +function count_vulnerabilities() { + local severity="$1" + local result_path="$2" + jq ".result.summary.$severity" "$result_path" } +# Counts the number of fixable vulnerabilities for a given severity. +function count_fixable_vulnerabilities() { + local severity="$1" + local result_path="$2" + jq "[.result.vulnerabilities // [] | .[] | select(.cveSeverity == \"$severity\" and .componentFixedVersion != \"\")] | length" "$result_path" +} + +# Prints the vulnerability status and an overview table of the vulnerabilities counts. function print_vulnerability_status() { local -n vuln_counts_ref=$1 local -n fixable_counts_ref=$2 @@ -74,33 +101,19 @@ function print_vulnerability_status() { done } -result_path="scan-result.json" -scan_image "$IMAGE" "$VERSION" "$result_path" - -# Count the number of vulnerabilities and fixable vulnerabilities for each severity. -# Use associative arrays to store counts by severity. -# Arrays are used via nameref parameters in print_vulnerability_status function. -# shellcheck disable=SC2034 -declare -A vuln_counts -# shellcheck disable=SC2034 -declare -A fixable_counts - -# shellcheck disable=SC2034 -for severity in CRITICAL IMPORTANT MODERATE; do - vuln_counts[$severity]=$(count_vulnerabilities "$severity" "$result_path") - fixable_counts[$severity]=$(count_fixable_vulnerabilities "$severity" "$result_path") -done - -# Print the summary of the vulnerabilities. -gh_summary "### $SUMMARY_PREFIX $IMAGE:$VERSION" -print_vulnerability_status vuln_counts fixable_counts - -# Print the vulnerabilities table in a collapsible section. -# For the table to render correctly, we need to add a newline after the summary. -gh_summary "
Click to expand details\n" -gh_summary "$(print_table "$result_path")" -gh_summary "
" - -if [[ -f failure_flag ]]; then - exit 1 -fi +# Prints a markdown table of the vulnerabilities, sorted by severity. +function print_table() { + local result_path="$1" + jq -r ' + .result.vulnerabilities // [] + | (["COMPONENT","VERSION","CVE","SEVERITY","FIXED_VERSION","LINK"] | @csv), + (["---","---","---","---","---","---"] | @csv), + (.[] | [.componentName // "", .componentVersion // "", .cveId // "", .cveSeverity // "", .componentFixedVersion // "", .cveInfo // ""] | @csv) + ' <(cat "$result_path") \ + | sed 's/,/ | /g' \ + | sed 's/^/| /' \ + | sed 's/$/ |/' \ + | sed 's/"//g' +} + +main "$IMAGE" "$VERSION" "$SUMMARY_PREFIX" From 09af7014ca00b6a82d62a62abb4d6c35d4facc26 Mon Sep 17 00:00:00 2001 From: Tom Martensen Date: Tue, 9 Dec 2025 13:16:59 +0100 Subject: [PATCH 15/29] add message explaining status --- .../check-image-vulnerabilities.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/release/check-image-vulnerabilities/check-image-vulnerabilities.sh b/release/check-image-vulnerabilities/check-image-vulnerabilities.sh index f27e8d3..96e9c3b 100755 --- a/release/check-image-vulnerabilities/check-image-vulnerabilities.sh +++ b/release/check-image-vulnerabilities/check-image-vulnerabilities.sh @@ -86,11 +86,14 @@ function print_vulnerability_status() { local -n fixable_counts_ref=$2 if (( fixable_counts_ref[CRITICAL] > 0 || fixable_counts_ref[IMPORTANT] > 0 )); then - gh_log "error" "Found fixable critical or important vulnerabilities. See the step summary for details." + local message="Found fixable critical or important vulnerabilities. See the step summary for details." + gh_log "error" "$message" touch failure_flag gh_summary "Status: ❌" + gh_summary "$message" else gh_summary "Status: ✅" + gh_summary "No fixable critical or important vulnerabilities found." fi gh_summary "" From a6584fdf58b82c10f89404d2397d504e51d3c7a2 Mon Sep 17 00:00:00 2001 From: Tom Martensen Date: Tue, 9 Dec 2025 13:23:25 +0100 Subject: [PATCH 16/29] re-format --- .../check-image-vulnerabilities.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/release/check-image-vulnerabilities/check-image-vulnerabilities.sh b/release/check-image-vulnerabilities/check-image-vulnerabilities.sh index 96e9c3b..9d093cd 100755 --- a/release/check-image-vulnerabilities/check-image-vulnerabilities.sh +++ b/release/check-image-vulnerabilities/check-image-vulnerabilities.sh @@ -90,10 +90,10 @@ function print_vulnerability_status() { gh_log "error" "$message" touch failure_flag gh_summary "Status: ❌" - gh_summary "$message" + gh_summary "> $message" else gh_summary "Status: ✅" - gh_summary "No fixable critical or important vulnerabilities found." + gh_summary "> No fixable critical or important vulnerabilities found." fi gh_summary "" From 6e4b6764999335dbf9b9880aab1def78e788bac2 Mon Sep 17 00:00:00 2001 From: Tom Martensen Date: Tue, 9 Dec 2025 13:38:11 +0100 Subject: [PATCH 17/29] add comments for the csv->markdown conversion --- .../check-image-vulnerabilities.sh | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/release/check-image-vulnerabilities/check-image-vulnerabilities.sh b/release/check-image-vulnerabilities/check-image-vulnerabilities.sh index 9d093cd..51cae51 100755 --- a/release/check-image-vulnerabilities/check-image-vulnerabilities.sh +++ b/release/check-image-vulnerabilities/check-image-vulnerabilities.sh @@ -107,16 +107,20 @@ function print_vulnerability_status() { # Prints a markdown table of the vulnerabilities, sorted by severity. function print_table() { local result_path="$1" - jq -r ' - .result.vulnerabilities // [] - | (["COMPONENT","VERSION","CVE","SEVERITY","FIXED_VERSION","LINK"] | @csv), - (["---","---","---","---","---","---"] | @csv), - (.[] | [.componentName // "", .componentVersion // "", .cveId // "", .cveSeverity // "", .componentFixedVersion // "", .cveInfo // ""] | @csv) - ' <(cat "$result_path") \ - | sed 's/,/ | /g' \ - | sed 's/^/| /' \ - | sed 's/$/ |/' \ - | sed 's/"//g' + # Convert jq CSV output to markdown table format: + # - Replace commas with markdown column separators + # - Add left and right borders to create table rows + # - Remove CSV quotes + jq -r ' + .result.vulnerabilities // [] + | (["COMPONENT","VERSION","CVE","SEVERITY","FIXED_VERSION","LINK"] | @csv), + (["---","---","---","---","---","---"] | @csv), + (.[] | [.componentName // "", .componentVersion // "", .cveId // "", .cveSeverity // "", .componentFixedVersion // "", .cveInfo // ""] | @csv) + ' <(cat "$result_path") \ + | sed 's/,/ | /g' \ + | sed 's/^/| /' \ + | sed 's/$/ |/' \ + | sed 's/"//g' } main "$IMAGE" "$VERSION" "$SUMMARY_PREFIX" From 75c2a6d5d15869bd1f69e524b7beb49022611190 Mon Sep 17 00:00:00 2001 From: Tom Martensen Date: Tue, 9 Dec 2025 13:45:20 +0100 Subject: [PATCH 18/29] touches --- .../check-image-vulnerabilities.sh | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/release/check-image-vulnerabilities/check-image-vulnerabilities.sh b/release/check-image-vulnerabilities/check-image-vulnerabilities.sh index 51cae51..00d5016 100755 --- a/release/check-image-vulnerabilities/check-image-vulnerabilities.sh +++ b/release/check-image-vulnerabilities/check-image-vulnerabilities.sh @@ -36,8 +36,8 @@ function main() { # shellcheck disable=SC2034 for severity in CRITICAL IMPORTANT MODERATE; do - vuln_counts[$severity]=$(count_vulnerabilities "$severity" "$result_path") - fixable_counts[$severity]=$(count_fixable_vulnerabilities "$severity" "$result_path") + vuln_counts[$severity]="$(count_vulnerabilities "$severity" "$result_path")" + fixable_counts[$severity]="$(count_fixable_vulnerabilities "$severity" "$result_path")" done # Print the summary of the vulnerabilities. @@ -47,9 +47,11 @@ function main() { # Print the vulnerabilities table in a collapsible section. # For the table to render correctly, we need to add a newline after the summary. gh_summary "
Click to expand details\n" - gh_summary "$(print_table "$result_path")" + gh_summary "$(print_vulnerabilities_table "$result_path")" gh_summary "
" + # If the failure flag is produced by print_vulnerability_status, + # exit with a failure status. if [[ -f failure_flag ]]; then exit 1 fi @@ -87,6 +89,7 @@ function print_vulnerability_status() { if (( fixable_counts_ref[CRITICAL] > 0 || fixable_counts_ref[IMPORTANT] > 0 )); then local message="Found fixable critical or important vulnerabilities. See the step summary for details." + gh_log "error" "$message" touch failure_flag gh_summary "Status: ❌" @@ -105,7 +108,7 @@ function print_vulnerability_status() { } # Prints a markdown table of the vulnerabilities, sorted by severity. -function print_table() { +function print_vulnerabilities_table() { local result_path="$1" # Convert jq CSV output to markdown table format: # - Replace commas with markdown column separators From 92b555bb1fa1b05ae160717d0a9d9eb9e2a28100 Mon Sep 17 00:00:00 2001 From: Tom Martensen Date: Tue, 9 Dec 2025 13:56:57 +0100 Subject: [PATCH 19/29] update README --- release/check-image-vulnerabilities/README.md | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/release/check-image-vulnerabilities/README.md b/release/check-image-vulnerabilities/README.md index 988f91f..c7caccb 100644 --- a/release/check-image-vulnerabilities/README.md +++ b/release/check-image-vulnerabilities/README.md @@ -14,14 +14,14 @@ permissions: ## All options -| Input | Description | Default | -| ----------------------------------------- | -------------------------------------------------- | --------- | -| [image](#image) | Image name (without registry prefix) | | -| [version](#version) | Image version tag | | -| [wait-limit](#wait-limit) | Maximum time to wait for image (seconds) | `"7200"` | -| [summary-title](#summary-title) | Title prefix for the GitHub step summary | | -| [quay-bearer-token](#quay-bearer-token) | Quay.io bearer token for wait-for-image | | -| [central-url](#central-url) | ACS Central URL | | +| Input | Description | Required | Default | +| ----------------------------------------- | -------------------------------------------------- | -------- | --------- | +| [image](#image) | Image name (without registry prefix) | Yes | | +| [version](#version) | Image version tag | Yes | | +| [wait-limit](#wait-limit) | Maximum time to wait for image (seconds) | No | `"7200"` | +| [summary-prefix](#summary-prefix) | Title prefix for the GitHub step summary | Yes | | +| [quay-bearer-token](#quay-bearer-token) | Quay.io bearer token for wait-for-image | Yes | | +| [central-url](#central-url) | ACS Central URL | Yes | | ### Detailed options @@ -29,9 +29,9 @@ permissions: Image name without the registry prefix. The action will automatically prepend `quay.io/` to construct the full image reference. -Example: `"main"` +Example: `"rhacs-eng/main"` -Default value: unset +Required: Yes #### version @@ -39,27 +39,29 @@ Image version tag to scan. Example: `"3.76.1"` -Default value: unset +Required: Yes #### wait-limit Maximum time in seconds to wait for the image to be available on Quay.io before failing. -Default value: `"7200"` (2 hours) +Required: No -#### summary-title +Default: `"7200"` (2 hours) + +#### summary-prefix Title prefix for the vulnerability report in the GitHub step summary. This helps identify which image the scan results correspond to when multiple scans are performed in a workflow. Example: `"Image Scan Results"` -Default value: unset +Required: Yes #### quay-bearer-token Bearer token for authenticating with the Quay.io API. This is required by the wait-for-image action to check if the image is available. -Default value: unset +Required: Yes #### central-url @@ -67,7 +69,7 @@ URL of the ACS/RHACS Central instance to use for scanning. Example: `"https://central.example.com"` -Default value: unset +Required: Yes ## Usage @@ -86,7 +88,7 @@ jobs: with: image: rhacs-eng/main version: 3.76.1 - summary-title: "Main Image Scan" + summary-prefix: "Main Image Scan" quay-bearer-token: ${{ secrets.QUAY_BEARER_TOKEN }} central-url: https://central.example.com ``` @@ -100,7 +102,7 @@ The action performs the following steps: 3. **Install roxctl**: Installs the roxctl CLI tool 4. **Scan image**: Scans the image for vulnerabilities 5. **Generate report**: Outputs a formatted table of vulnerabilities to the GitHub step summary -6. **Check results**: Fails the workflow if any CRITICAL or IMPORTANT vulnerabilities are found +6. **Check results**: Fails the workflow if any **fixable** CRITICAL or IMPORTANT vulnerabilities are found If vulnerabilities are found, the step summary will include: From 05a2a6e8786eb94b915062481efe5e558017b70b Mon Sep 17 00:00:00 2001 From: Tom Martensen Date: Sat, 13 Dec 2025 11:44:37 +0100 Subject: [PATCH 20/29] prefer 'scan' or 'check' terminology --- .../README.md | 4 ++-- .../action.yml | 6 +++--- .../scan-image-vulnerabilities.sh} | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) rename release/{check-image-vulnerabilities => scan-image-vulnerabilities}/README.md (97%) rename release/{check-image-vulnerabilities => scan-image-vulnerabilities}/action.yml (90%) rename release/{check-image-vulnerabilities/check-image-vulnerabilities.sh => scan-image-vulnerabilities/scan-image-vulnerabilities.sh} (95%) diff --git a/release/check-image-vulnerabilities/README.md b/release/scan-image-vulnerabilities/README.md similarity index 97% rename from release/check-image-vulnerabilities/README.md rename to release/scan-image-vulnerabilities/README.md index c7caccb..64f18a1 100644 --- a/release/check-image-vulnerabilities/README.md +++ b/release/scan-image-vulnerabilities/README.md @@ -1,4 +1,4 @@ -# Check Image Vulnerabilities +# Scan Image Vulnerabilities Scan a container image on quay.io for vulnerabilities using `roxctl image scan` and fail if fixable critical or important vulnerabilities are found. @@ -84,7 +84,7 @@ jobs: permissions: id-token: write # Required for ACS Central OIDC login steps: - - uses: stackrox/actions/release/check-image-vulnerabilities@v1 + - uses: stackrox/actions/release/scan-image-vulnerabilities@v1 with: image: rhacs-eng/main version: 3.76.1 diff --git a/release/check-image-vulnerabilities/action.yml b/release/scan-image-vulnerabilities/action.yml similarity index 90% rename from release/check-image-vulnerabilities/action.yml rename to release/scan-image-vulnerabilities/action.yml index 9d6b95e..b6628ac 100644 --- a/release/check-image-vulnerabilities/action.yml +++ b/release/scan-image-vulnerabilities/action.yml @@ -1,4 +1,4 @@ -name: "Check Image Vulnerabilities" +name: "Scan Image Vulnerabilities" description: "Scan an image for vulnerabilities and fail if fixable critical or important ones are found" inputs: @@ -43,12 +43,12 @@ runs: central-endpoint: ${{ inputs.central-url }} central-token: ${{ env.ROX_API_TOKEN }} - - name: Scan image and check for vulnerabilities + - name: Scan image for fixable vulnerabilities shell: bash run: | set -uo pipefail "${{ github.action_path }}/../../common/common.sh" \ - "${{ github.action_path }}/check-image-vulnerabilities.sh" \ + "${{ github.action_path }}/scan-image-vulnerabilities.sh" \ "${{ inputs.image }}" \ "${{ inputs.version }}" \ "${{ inputs.summary-prefix }}" diff --git a/release/check-image-vulnerabilities/check-image-vulnerabilities.sh b/release/scan-image-vulnerabilities/scan-image-vulnerabilities.sh similarity index 95% rename from release/check-image-vulnerabilities/check-image-vulnerabilities.sh rename to release/scan-image-vulnerabilities/scan-image-vulnerabilities.sh index 00d5016..595a310 100755 --- a/release/check-image-vulnerabilities/check-image-vulnerabilities.sh +++ b/release/scan-image-vulnerabilities/scan-image-vulnerabilities.sh @@ -1,10 +1,10 @@ #!/usr/bin/env bash # -# Counts the number of fixable vulnerabilities in the scan result. +# Scans an image for vulnerabilities and prints a summary of the vulnerabilities. # # Local run: # -# test/local-env.sh release/check-image-vulnerabilities/check-image-vulnerabilities.sh +# test/local-env.sh release/scan-image-vulnerabilities/scan-image-vulnerabilities.sh # set -euo pipefail From e6f53b0798170a8fb7f3116887bbfd52fd436389 Mon Sep 17 00:00:00 2001 From: Tom Martensen Date: Sat, 13 Dec 2025 12:44:44 +0100 Subject: [PATCH 21/29] addressing reviewer feedback --- common/common.sh | 2 + release/scan-image-vulnerabilities/action.yml | 3 +- .../scan-image-vulnerabilities.sh | 74 ++++++++----------- 3 files changed, 33 insertions(+), 46 deletions(-) diff --git a/common/common.sh b/common/common.sh index 1d2e703..5db891e 100755 --- a/common/common.sh +++ b/common/common.sh @@ -1,5 +1,7 @@ #!/usr/bin/env bash +set -euo pipefail + # The output appears in a dedicated emphasized GitHub step box. # Multiple lines are not supported. # Markdown is not supported. diff --git a/release/scan-image-vulnerabilities/action.yml b/release/scan-image-vulnerabilities/action.yml index b6628ac..7314fc3 100644 --- a/release/scan-image-vulnerabilities/action.yml +++ b/release/scan-image-vulnerabilities/action.yml @@ -49,6 +49,5 @@ runs: set -uo pipefail "${{ github.action_path }}/../../common/common.sh" \ "${{ github.action_path }}/scan-image-vulnerabilities.sh" \ - "${{ inputs.image }}" \ - "${{ inputs.version }}" \ + "quay.io/${{ inputs.image }}:${{ inputs.version }}" \ "${{ inputs.summary-prefix }}" diff --git a/release/scan-image-vulnerabilities/scan-image-vulnerabilities.sh b/release/scan-image-vulnerabilities/scan-image-vulnerabilities.sh index 595a310..1227905 100755 --- a/release/scan-image-vulnerabilities/scan-image-vulnerabilities.sh +++ b/release/scan-image-vulnerabilities/scan-image-vulnerabilities.sh @@ -1,30 +1,24 @@ #!/usr/bin/env bash # # Scans an image for vulnerabilities and prints a summary of the vulnerabilities. +# For the purpose of this script, a vulnerability is a component in a version that is affected by a CVE. # # Local run: # -# test/local-env.sh release/scan-image-vulnerabilities/scan-image-vulnerabilities.sh +# test/local-env.sh release/scan-image-vulnerabilities/scan-image-vulnerabilities.sh # set -euo pipefail -IMAGE="${1:-}" -VERSION="${2:-}" -SUMMARY_PREFIX="${3:-}" - -check_not_empty \ - IMAGE \ - VERSION \ - SUMMARY_PREFIX - function main() { - local image="$1" - local version="$2" - local summary_prefix="$3" + IMAGE="${1:-}" + SUMMARY_PREFIX="${2:-}" local result_path="scan-result.json" - # Scan the image for vulnerabilities. - scan_image "$image" "$version" "$result_path" + check_not_empty \ + IMAGE \ + SUMMARY_PREFIX + + scan_image "$IMAGE" "$result_path" # Count the number of vulnerabilities and fixable vulnerabilities for each severity. # Use associative arrays to store counts by severity. @@ -35,13 +29,12 @@ function main() { declare -A fixable_counts # shellcheck disable=SC2034 - for severity in CRITICAL IMPORTANT MODERATE; do + for severity in CRITICAL IMPORTANT MODERATE LOW; do vuln_counts[$severity]="$(count_vulnerabilities "$severity" "$result_path")" fixable_counts[$severity]="$(count_fixable_vulnerabilities "$severity" "$result_path")" done - # Print the summary of the vulnerabilities. - gh_summary "### $summary_prefix $image:$version" + gh_summary "### $SUMMARY_PREFIX $IMAGE" print_vulnerability_status vuln_counts fixable_counts # Print the vulnerabilities table in a collapsible section. @@ -50,21 +43,21 @@ function main() { gh_summary "$(print_vulnerabilities_table "$result_path")" gh_summary "
" - # If the failure flag is produced by print_vulnerability_status, - # exit with a failure status. - if [[ -f failure_flag ]]; then - exit 1 - fi + # Fail the build if any fixable critical or important vulnerabilities are found. + severities=( "CRITICAL" "IMPORTANT" ) + for severity in "${severities[@]}"; do + if (( fixable_counts[$severity] > 0 )); then + exit 1 + fi + done } # Scans the image for vulnerabilities. function scan_image() { local image="$1" - local version="$2" - local result_path="$3" + local result_path="$2" roxctl image scan --output=json --force \ - --severity="MODERATE,IMPORTANT,CRITICAL" \ - --image="quay.io/${image}:${version}" > "$result_path" + --image="${image}" | tee "$result_path" gh_output result-path "$result_path" } @@ -88,10 +81,9 @@ function print_vulnerability_status() { local -n fixable_counts_ref=$2 if (( fixable_counts_ref[CRITICAL] > 0 || fixable_counts_ref[IMPORTANT] > 0 )); then - local message="Found fixable critical or important vulnerabilities. See the step summary for details." + local message="Found fixable critical or important vulnerabilities." - gh_log "error" "$message" - touch failure_flag + gh_log "error" "$message See the step summary for details." gh_summary "Status: ❌" gh_summary "> $message" else @@ -102,28 +94,22 @@ function print_vulnerability_status() { gh_summary "" gh_summary "| Severity | Total | Fixable |" gh_summary "| --- | --- | --- |" - for severity in CRITICAL IMPORTANT MODERATE; do + for severity in CRITICAL IMPORTANT MODERATE LOW; do gh_summary "| $severity | ${vuln_counts_ref[$severity]} | ${fixable_counts_ref[$severity]} |" done } # Prints a markdown table of the vulnerabilities, sorted by severity. +# Each row contains left, right and column separators added by jq's join function. function print_vulnerabilities_table() { local result_path="$1" - # Convert jq CSV output to markdown table format: - # - Replace commas with markdown column separators - # - Add left and right borders to create table rows - # - Remove CSV quotes jq -r ' .result.vulnerabilities // [] - | (["COMPONENT","VERSION","CVE","SEVERITY","FIXED_VERSION","LINK"] | @csv), - (["---","---","---","---","---","---"] | @csv), - (.[] | [.componentName // "", .componentVersion // "", .cveId // "", .cveSeverity // "", .componentFixedVersion // "", .cveInfo // ""] | @csv) - ' <(cat "$result_path") \ - | sed 's/,/ | /g' \ - | sed 's/^/| /' \ - | sed 's/$/ |/' \ - | sed 's/"//g' + | sort_by({"CRITICAL":0,"IMPORTANT":1,"MODERATE":2,"LOW":3}[.cveSeverity] // 4) + | (["COMPONENT","VERSION","CVE","SEVERITY","FIXED_VERSION","LINK"] | "| " + join(" | ") + " |"), + (["---","---","---","---","---","---"] | "| " + join(" | ") + " |"), + (.[] | [.componentName // "", .componentVersion // "", .cveId // "", .cveSeverity // "", .componentFixedVersion // "", .cveInfo // ""] | "| " + join(" | ") + " |") + ' "$result_path" } -main "$IMAGE" "$VERSION" "$SUMMARY_PREFIX" +main "$@" From 02bdcb203481a1b410375b2aac5dadbcaac06313 Mon Sep 17 00:00:00 2001 From: Tom Martensen Date: Sat, 13 Dec 2025 13:00:44 +0100 Subject: [PATCH 22/29] update script description --- .../scan-image-vulnerabilities/scan-image-vulnerabilities.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release/scan-image-vulnerabilities/scan-image-vulnerabilities.sh b/release/scan-image-vulnerabilities/scan-image-vulnerabilities.sh index 1227905..537def3 100755 --- a/release/scan-image-vulnerabilities/scan-image-vulnerabilities.sh +++ b/release/scan-image-vulnerabilities/scan-image-vulnerabilities.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # # Scans an image for vulnerabilities and prints a summary of the vulnerabilities. -# For the purpose of this script, a vulnerability is a component in a version that is affected by a CVE. +# For the purpose of this script, a vulnerability is an image component in a version that is affected by a CVE. # # Local run: # From eb950c69cf21ff72c7232375f7c0249abe0ac661 Mon Sep 17 00:00:00 2001 From: Tom Martensen Date: Fri, 19 Dec 2025 11:15:10 +0100 Subject: [PATCH 23/29] update README to address review comments --- release/scan-image-vulnerabilities/README.md | 45 ++++---------------- 1 file changed, 9 insertions(+), 36 deletions(-) diff --git a/release/scan-image-vulnerabilities/README.md b/release/scan-image-vulnerabilities/README.md index 64f18a1..ae3845d 100644 --- a/release/scan-image-vulnerabilities/README.md +++ b/release/scan-image-vulnerabilities/README.md @@ -1,8 +1,8 @@ # Scan Image Vulnerabilities -Scan a container image on quay.io for vulnerabilities using `roxctl image scan` and fail if fixable critical or important vulnerabilities are found. +Scan a container image in Quay.io for vulnerabilities using `roxctl image scan` and fail if fixable critical or important vulnerabilities are found. -This action waits for an image to be available on Quay.io, scans it using roxctl, and generates a detailed vulnerability report in the GitHub step summary, while making the raw report available as JSON in the workspace as `scan-result.json`. +This action waits for an image to be available in Quay.io, scans it using roxctl, and generates a detailed vulnerability report in the GitHub step summary, while making the raw report available as JSON in the workspace as `scan-result.json`. ## Required permissions @@ -31,49 +31,40 @@ Image name without the registry prefix. The action will automatically prepend `q Example: `"rhacs-eng/main"` -Required: Yes - #### version Image version tag to scan. Example: `"3.76.1"` -Required: Yes - #### wait-limit Maximum time in seconds to wait for the image to be available on Quay.io before failing. -Required: No - Default: `"7200"` (2 hours) #### summary-prefix -Title prefix for the vulnerability report in the GitHub step summary. This helps identify which image the scan results correspond to when multiple scans are performed in a workflow. - -Example: `"Image Scan Results"` - -Required: Yes +Prefix for the vulnerability report in the GitHub step summary. Use this to help users of the action classify images into groups when multiple matrix scans are performed in a workflow. + +Example: `"Upstream Image Scan Results"` #### quay-bearer-token Bearer token for authenticating with the Quay.io API. This is required by the wait-for-image action to check if the image is available. -Required: Yes - #### central-url URL of the ACS/RHACS Central instance to use for scanning. Example: `"https://central.example.com"` -Required: Yes - ## Usage -The action requires credentials for ACS Central to be available. It integrates with the `stackrox/central-login@v1` action which uses OIDC authentication. +The action integrates with the [stackrox/central-login](https://github.com/stackrox/central-login) action, which uses OIDC login for authentication of the `roxctl` CLI. +The ACS Central needs to be configured to allow exchanging tokens from GitHub Actions workflow runs. + +Additionally, an image integration for Quay.io needs to be configured in the ACS Central. ```yaml name: Scan image for vulnerabilities @@ -92,21 +83,3 @@ jobs: quay-bearer-token: ${{ secrets.QUAY_BEARER_TOKEN }} central-url: https://central.example.com ``` - -## Behavior - -The action performs the following steps: - -1. **Wait for image**: Waits for `quay.io/$IMAGE:$VERSION` to be available on Quay.io -2. **Login to Central**: Authenticates with ACS Central using OIDC -3. **Install roxctl**: Installs the roxctl CLI tool -4. **Scan image**: Scans the image for vulnerabilities -5. **Generate report**: Outputs a formatted table of vulnerabilities to the GitHub step summary -6. **Check results**: Fails the workflow if any **fixable** CRITICAL or IMPORTANT vulnerabilities are found - -If vulnerabilities are found, the step summary will include: - -- Component name and version -- CVE ID and severity -- Fixed version (if available) -- Link to CVE information From 380f2f4693abb1cf39763c96c7487035fcf119d2 Mon Sep 17 00:00:00 2001 From: Tom Martensen Date: Fri, 19 Dec 2025 11:35:37 +0100 Subject: [PATCH 24/29] extract vuln table header from jq --- .../scan-image-vulnerabilities.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/release/scan-image-vulnerabilities/scan-image-vulnerabilities.sh b/release/scan-image-vulnerabilities/scan-image-vulnerabilities.sh index 537def3..4f31a60 100755 --- a/release/scan-image-vulnerabilities/scan-image-vulnerabilities.sh +++ b/release/scan-image-vulnerabilities/scan-image-vulnerabilities.sh @@ -103,12 +103,12 @@ function print_vulnerability_status() { # Each row contains left, right and column separators added by jq's join function. function print_vulnerabilities_table() { local result_path="$1" + echo "| COMPONENT | VERSION | CVE | SEVERITY | FIXED_VERSION | LINK |" + echo "| --- | --- | --- | --- | --- | --- |" jq -r ' .result.vulnerabilities // [] | sort_by({"CRITICAL":0,"IMPORTANT":1,"MODERATE":2,"LOW":3}[.cveSeverity] // 4) - | (["COMPONENT","VERSION","CVE","SEVERITY","FIXED_VERSION","LINK"] | "| " + join(" | ") + " |"), - (["---","---","---","---","---","---"] | "| " + join(" | ") + " |"), - (.[] | [.componentName // "", .componentVersion // "", .cveId // "", .cveSeverity // "", .componentFixedVersion // "", .cveInfo // ""] | "| " + join(" | ") + " |") + | (.[] | [.componentName // "", .componentVersion // "", .cveId // "", .cveSeverity // "", .componentFixedVersion // "", .cveInfo // ""] | "| " + join(" | ") + " |") ' "$result_path" } From 01751fbd777c98f4d411cb555890d88f86dbeb8c Mon Sep 17 00:00:00 2001 From: Tom Martensen Date: Fri, 19 Dec 2025 11:46:47 +0100 Subject: [PATCH 25/29] put fixable findings first in table, then unfixable; fix counting of total findings by not relying on roxctl summary --- .../scan-image-vulnerabilities.sh | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/release/scan-image-vulnerabilities/scan-image-vulnerabilities.sh b/release/scan-image-vulnerabilities/scan-image-vulnerabilities.sh index 4f31a60..45be19f 100755 --- a/release/scan-image-vulnerabilities/scan-image-vulnerabilities.sh +++ b/release/scan-image-vulnerabilities/scan-image-vulnerabilities.sh @@ -65,7 +65,7 @@ function scan_image() { function count_vulnerabilities() { local severity="$1" local result_path="$2" - jq ".result.summary.$severity" "$result_path" + jq "[.result.vulnerabilities // [] | .[] | select(.cveSeverity == \"$severity\")] | length" "$result_path" } # Counts the number of fixable vulnerabilities for a given severity. @@ -99,7 +99,7 @@ function print_vulnerability_status() { done } -# Prints a markdown table of the vulnerabilities, sorted by severity. +# Prints a markdown table of the vulnerabilities, sorted by severity with fixable findings first. # Each row contains left, right and column separators added by jq's join function. function print_vulnerabilities_table() { local result_path="$1" @@ -107,7 +107,10 @@ function print_vulnerabilities_table() { echo "| --- | --- | --- | --- | --- | --- |" jq -r ' .result.vulnerabilities // [] - | sort_by({"CRITICAL":0,"IMPORTANT":1,"MODERATE":2,"LOW":3}[.cveSeverity] // 4) + | sort_by([ + (if ((.componentFixedVersion // "") != "") then 0 else 1 end), + ({"CRITICAL":0,"IMPORTANT":1,"MODERATE":2,"LOW":3}[.cveSeverity] // 4) + ]) | (.[] | [.componentName // "", .componentVersion // "", .cveId // "", .cveSeverity // "", .componentFixedVersion // "", .cveInfo // ""] | "| " + join(" | ") + " |") ' "$result_path" } From 7334b3bc3581ec89b3df46ac64b9020e261fdfcb Mon Sep 17 00:00:00 2001 From: Tom Martensen Date: Fri, 19 Dec 2025 11:54:01 +0100 Subject: [PATCH 26/29] s/vulnerabilities/findings --- .../scan-image-vulnerabilities.sh | 43 ++++++++++--------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/release/scan-image-vulnerabilities/scan-image-vulnerabilities.sh b/release/scan-image-vulnerabilities/scan-image-vulnerabilities.sh index 45be19f..d3f4ce8 100755 --- a/release/scan-image-vulnerabilities/scan-image-vulnerabilities.sh +++ b/release/scan-image-vulnerabilities/scan-image-vulnerabilities.sh @@ -1,7 +1,8 @@ #!/usr/bin/env bash # -# Scans an image for vulnerabilities and prints a summary of the vulnerabilities. -# For the purpose of this script, a vulnerability is an image component in a version that is affected by a CVE. +# Scans an image for vulnerabilities and prints a summary of the findings. +# For the purpose of this script, a finding is an image component in a version that is affected by a CVE. +# There may be multiple findings for the same image component in a version, if the component has multiple CVEs. # # Local run: # @@ -20,30 +21,30 @@ function main() { scan_image "$IMAGE" "$result_path" - # Count the number of vulnerabilities and fixable vulnerabilities for each severity. + # Count the total and fixable number of findings for each severity. # Use associative arrays to store counts by severity. - # Arrays are used via nameref parameters in print_vulnerability_status function. + # Arrays are used via nameref parameters in print_findings_status function. # shellcheck disable=SC2034 - declare -A vuln_counts + declare -A total_counts # shellcheck disable=SC2034 declare -A fixable_counts # shellcheck disable=SC2034 for severity in CRITICAL IMPORTANT MODERATE LOW; do - vuln_counts[$severity]="$(count_vulnerabilities "$severity" "$result_path")" - fixable_counts[$severity]="$(count_fixable_vulnerabilities "$severity" "$result_path")" + total_counts[$severity]="$(count_total_findings "$severity" "$result_path")" + fixable_counts[$severity]="$(count_fixable_findings "$severity" "$result_path")" done gh_summary "### $SUMMARY_PREFIX $IMAGE" - print_vulnerability_status vuln_counts fixable_counts + print_findings_status total_counts fixable_counts - # Print the vulnerabilities table in a collapsible section. + # Print the findings table in a collapsible section. # For the table to render correctly, we need to add a newline after the summary. gh_summary "
Click to expand details\n" - gh_summary "$(print_vulnerabilities_table "$result_path")" + gh_summary "$(print_findings_table "$result_path")" gh_summary "
" - # Fail the build if any fixable critical or important vulnerabilities are found. + # Fail the build if any fixable critical or important findings. severities=( "CRITICAL" "IMPORTANT" ) for severity in "${severities[@]}"; do if (( fixable_counts[$severity] > 0 )); then @@ -61,23 +62,23 @@ function scan_image() { gh_output result-path "$result_path" } -# Counts the number of vulnerabilities for a given severity. -function count_vulnerabilities() { +# Counts the number of findings for a given severity. +function count_total_findings() { local severity="$1" local result_path="$2" jq "[.result.vulnerabilities // [] | .[] | select(.cveSeverity == \"$severity\")] | length" "$result_path" } -# Counts the number of fixable vulnerabilities for a given severity. -function count_fixable_vulnerabilities() { +# Counts the number of fixable findings for a given severity. +function count_fixable_findings() { local severity="$1" local result_path="$2" jq "[.result.vulnerabilities // [] | .[] | select(.cveSeverity == \"$severity\" and .componentFixedVersion != \"\")] | length" "$result_path" } -# Prints the vulnerability status and an overview table of the vulnerabilities counts. -function print_vulnerability_status() { - local -n vuln_counts_ref=$1 +# Prints the vulnerability status and an overview table of the findings counts. +function print_findings_status() { + local -n total_counts_ref=$1 local -n fixable_counts_ref=$2 if (( fixable_counts_ref[CRITICAL] > 0 || fixable_counts_ref[IMPORTANT] > 0 )); then @@ -95,13 +96,13 @@ function print_vulnerability_status() { gh_summary "| Severity | Total | Fixable |" gh_summary "| --- | --- | --- |" for severity in CRITICAL IMPORTANT MODERATE LOW; do - gh_summary "| $severity | ${vuln_counts_ref[$severity]} | ${fixable_counts_ref[$severity]} |" + gh_summary "| $severity | ${total_counts_ref[$severity]} | ${fixable_counts_ref[$severity]} |" done } -# Prints a markdown table of the vulnerabilities, sorted by severity with fixable findings first. +# Prints a markdown table of the findings, sorted by severity with fixable findings first. # Each row contains left, right and column separators added by jq's join function. -function print_vulnerabilities_table() { +function print_findings_table() { local result_path="$1" echo "| COMPONENT | VERSION | CVE | SEVERITY | FIXED_VERSION | LINK |" echo "| --- | --- | --- | --- | --- | --- |" From b7c19e539bd5185e44ef7e090b7cf7f2e14a4eca Mon Sep 17 00:00:00 2001 From: Tom Martensen Date: Fri, 19 Dec 2025 12:31:39 +0100 Subject: [PATCH 27/29] refactor fixable findings assertion to common function --- .../scan-image-vulnerabilities.sh | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/release/scan-image-vulnerabilities/scan-image-vulnerabilities.sh b/release/scan-image-vulnerabilities/scan-image-vulnerabilities.sh index d3f4ce8..e508730 100755 --- a/release/scan-image-vulnerabilities/scan-image-vulnerabilities.sh +++ b/release/scan-image-vulnerabilities/scan-image-vulnerabilities.sh @@ -44,13 +44,10 @@ function main() { gh_summary "$(print_findings_table "$result_path")" gh_summary "" - # Fail the build if any fixable critical or important findings. - severities=( "CRITICAL" "IMPORTANT" ) - for severity in "${severities[@]}"; do - if (( fixable_counts[$severity] > 0 )); then - exit 1 - fi - done + # Fail the build if any fixable findings of relevant severity are present. + if [ "$(assert_fixable_findings_present fixable_counts)" = "true" ]; then + exit 1 + fi } # Scans the image for vulnerabilities. @@ -81,7 +78,7 @@ function print_findings_status() { local -n total_counts_ref=$1 local -n fixable_counts_ref=$2 - if (( fixable_counts_ref[CRITICAL] > 0 || fixable_counts_ref[IMPORTANT] > 0 )); then + if [ "$(assert_fixable_findings_present fixable_counts_ref)" = "true" ]; then local message="Found fixable critical or important vulnerabilities." gh_log "error" "$message See the step summary for details." @@ -100,6 +97,16 @@ function print_findings_status() { done } +# Asserts if any fixable findings of relevant severity are present. +function assert_fixable_findings_present() { + local -n fixable_counts_map="$1" + if (( fixable_counts_map[CRITICAL] > 0 || fixable_counts_map[IMPORTANT] > 0 )); then + echo "true" + else + echo "false" + fi +} + # Prints a markdown table of the findings, sorted by severity with fixable findings first. # Each row contains left, right and column separators added by jq's join function. function print_findings_table() { From 303fbdbbf271242f30b1654b4115ef227bc90efc Mon Sep 17 00:00:00 2001 From: Tom Martensen Date: Mon, 5 Jan 2026 16:58:36 +0100 Subject: [PATCH 28/29] apply code suggestions --- release/scan-image-vulnerabilities/README.md | 6 +++--- .../scan-image-vulnerabilities.sh | 18 +++++++++--------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/release/scan-image-vulnerabilities/README.md b/release/scan-image-vulnerabilities/README.md index ae3845d..37944af 100644 --- a/release/scan-image-vulnerabilities/README.md +++ b/release/scan-image-vulnerabilities/README.md @@ -46,7 +46,7 @@ Default: `"7200"` (2 hours) #### summary-prefix Prefix for the vulnerability report in the GitHub step summary. Use this to help users of the action classify images into groups when multiple matrix scans are performed in a workflow. - + Example: `"Upstream Image Scan Results"` #### quay-bearer-token @@ -62,9 +62,9 @@ Example: `"https://central.example.com"` ## Usage The action integrates with the [stackrox/central-login](https://github.com/stackrox/central-login) action, which uses OIDC login for authentication of the `roxctl` CLI. -The ACS Central needs to be configured to allow exchanging tokens from GitHub Actions workflow runs. +The ACS Central needs to be configured to allow exchanging tokens from GitHub Actions workflow runs. -Additionally, an image integration for Quay.io needs to be configured in the ACS Central. +Additionally, an image integration for Quay.io must be configured in the ACS Central so that it can pull images requested for scanning. ```yaml name: Scan image for vulnerabilities diff --git a/release/scan-image-vulnerabilities/scan-image-vulnerabilities.sh b/release/scan-image-vulnerabilities/scan-image-vulnerabilities.sh index e508730..7d3ecfa 100755 --- a/release/scan-image-vulnerabilities/scan-image-vulnerabilities.sh +++ b/release/scan-image-vulnerabilities/scan-image-vulnerabilities.sh @@ -11,8 +11,8 @@ set -euo pipefail function main() { - IMAGE="${1:-}" - SUMMARY_PREFIX="${2:-}" + local IMAGE="${1:-}" + local SUMMARY_PREFIX="${2:-}" local result_path="scan-result.json" check_not_empty \ @@ -23,7 +23,7 @@ function main() { # Count the total and fixable number of findings for each severity. # Use associative arrays to store counts by severity. - # Arrays are used via nameref parameters in print_findings_status function. + # Arrays are used via nameref parameters in print_summary function. # shellcheck disable=SC2034 declare -A total_counts # shellcheck disable=SC2034 @@ -36,16 +36,16 @@ function main() { done gh_summary "### $SUMMARY_PREFIX $IMAGE" - print_findings_status total_counts fixable_counts + print_summary total_counts fixable_counts # Print the findings table in a collapsible section. # For the table to render correctly, we need to add a newline after the summary. gh_summary "
Click to expand details\n" - gh_summary "$(print_findings_table "$result_path")" + gh_summary "$(print_details_table "$result_path")" gh_summary "
" # Fail the build if any fixable findings of relevant severity are present. - if [ "$(assert_fixable_findings_present fixable_counts)" = "true" ]; then + if [[ "$(assert_fixable_findings_present fixable_counts)" = "true" ]]; then exit 1 fi } @@ -74,11 +74,11 @@ function count_fixable_findings() { } # Prints the vulnerability status and an overview table of the findings counts. -function print_findings_status() { +function print_summary() { local -n total_counts_ref=$1 local -n fixable_counts_ref=$2 - if [ "$(assert_fixable_findings_present fixable_counts_ref)" = "true" ]; then + if [[ "$(assert_fixable_findings_present fixable_counts_ref)" = "true" ]]; then local message="Found fixable critical or important vulnerabilities." gh_log "error" "$message See the step summary for details." @@ -109,7 +109,7 @@ function assert_fixable_findings_present() { # Prints a markdown table of the findings, sorted by severity with fixable findings first. # Each row contains left, right and column separators added by jq's join function. -function print_findings_table() { +function print_details_table() { local result_path="$1" echo "| COMPONENT | VERSION | CVE | SEVERITY | FIXED_VERSION | LINK |" echo "| --- | --- | --- | --- | --- | --- |" From 35cf37de45d65fe1d00b417c09c05de96223bb78 Mon Sep 17 00:00:00 2001 From: Tom Martensen Date: Mon, 5 Jan 2026 18:41:48 +0100 Subject: [PATCH 29/29] resolve are_blocking_vulns_present hint --- .../scan-image-vulnerabilities.sh | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/release/scan-image-vulnerabilities/scan-image-vulnerabilities.sh b/release/scan-image-vulnerabilities/scan-image-vulnerabilities.sh index 7d3ecfa..3e54594 100755 --- a/release/scan-image-vulnerabilities/scan-image-vulnerabilities.sh +++ b/release/scan-image-vulnerabilities/scan-image-vulnerabilities.sh @@ -45,7 +45,7 @@ function main() { gh_summary "" # Fail the build if any fixable findings of relevant severity are present. - if [[ "$(assert_fixable_findings_present fixable_counts)" = "true" ]]; then + if are_blocking_vulns_present fixable_counts; then exit 1 fi } @@ -78,7 +78,7 @@ function print_summary() { local -n total_counts_ref=$1 local -n fixable_counts_ref=$2 - if [[ "$(assert_fixable_findings_present fixable_counts_ref)" = "true" ]]; then + if are_blocking_vulns_present fixable_counts_ref; then local message="Found fixable critical or important vulnerabilities." gh_log "error" "$message See the step summary for details." @@ -97,14 +97,10 @@ function print_summary() { done } -# Asserts if any fixable findings of relevant severity are present. -function assert_fixable_findings_present() { +# Checks if any fixable findings of relevant severity are present. +function are_blocking_vulns_present() { local -n fixable_counts_map="$1" - if (( fixable_counts_map[CRITICAL] > 0 || fixable_counts_map[IMPORTANT] > 0 )); then - echo "true" - else - echo "false" - fi + (( fixable_counts_map[CRITICAL] > 0 || fixable_counts_map[IMPORTANT] > 0 )) } # Prints a markdown table of the findings, sorted by severity with fixable findings first.