# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 name: Trivy Changes on: pull_request: merge_group: types: [checks_requested] workflow_dispatch: inputs: base_sha: description: Base commit SHA to compare required: true type: string head_sha: description: Candidate commit SHA to compare required: true type: string permissions: contents: read concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: changes: name: Detect deployment configuration changes runs-on: ubuntu-latest permissions: contents: read pull-requests: read outputs: should_run: ${{ steps.default.outputs.should_run || steps.changed.outputs.any_modified }} base_sha: ${{ steps.comparison.outputs.base_sha }} steps: - id: default if: github.event_name != 'pull_request' run: echo "should_run=true" >> "$GITHUB_OUTPUT" - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 if: github.event_name == 'pull_request' with: ref: ${{ github.sha }} # Include both parents of the exact PR merge commit, even on reruns. fetch-depth: 2 persist-credentials: false - name: Resolve PR baseline id: comparison if: github.event_name == 'pull_request' run: | git rev-parse --verify HEAD^2 >/dev/null base_sha=$(git rev-parse --verify HEAD^1) echo "base_sha=$base_sha" >> "$GITHUB_OUTPUT" echo "Trivy comparison: $base_sha -> $(git rev-parse HEAD)" - id: changed if: github.event_name == 'pull_request' uses: tj-actions/changed-files@aa08304bd477b800d468db44fe10f6c61f7f7b11 # v42.1.0 with: base_sha: ${{ steps.comparison.outputs.base_sha }} sha: ${{ github.sha }} output_renamed_files_as_deleted_and_added: true # `any_modified` covers deletions, which `any_changed` omits, and a # failed diff has to fail the job: both otherwise report no relevant # change, and removing the scanner or a value fixture would skip the # scan behind a green status. fail_on_initial_diff_error: true files: | deploy/docker/** deploy/helm/** deploy/kube/** .trivyignore.yaml flake.nix flake.lock tasks/scripts/trivy-scan.sh tasks/scripts/trivy-scan-test.sh tasks/scripts/trivy-config-report.jq .github/workflows/trivy-scan.yml .github/workflows/trivy-changes.yml scan: name: Scan changed deployment configuration needs: changes if: needs.changes.outputs.should_run == 'true' runs-on: ubuntu-latest timeout-minutes: 30 env: BASE_REF: ${{ needs.changes.outputs.base_sha || github.event.merge_group.base_sha || inputs.base_sha }} HEAD_REF: ${{ inputs.head_sha || github.sha }} defaults: run: shell: nix develop --command bash -euo pipefail {0} steps: - name: Check out candidate uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: ref: ${{ env.HEAD_REF }} persist-credentials: false - name: Check out baseline uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: ref: ${{ env.BASE_REF }} path: .trivy-base persist-credentials: false - name: Set up Nix uses: ./.github/actions/setup-nix - name: Test report comparison run: tasks/scripts/trivy-scan-test.sh - name: Validate candidate ignore policy run: tasks/scripts/trivy-scan.sh validate-ignore # Ignore-policy changes take effect only after merge. Applying the # baseline policy to both scans prevents a candidate from exempting a new # finding in the same change that introduces it. - name: Prepare baseline ignore policy run: | if [ -f .trivy-base/.trivyignore.yaml ]; then cp .trivy-base/.trivyignore.yaml "$RUNNER_TEMP/trivy-baseline-ignore.yaml" else printf 'misconfigurations: []\n' >"$RUNNER_TEMP/trivy-baseline-ignore.yaml" fi - name: Scan baseline env: TRIVY_SOURCE_ROOT: ${{ github.workspace }}/.trivy-base TRIVY_IGNORE_FILE: ${{ runner.temp }}/trivy-baseline-ignore.yaml TRIVY_REPORT_DIR: ${{ runner.temp }}/trivy-base run: | mkdir -p "$TRIVY_REPORT_DIR" "$GITHUB_WORKSPACE/tasks/scripts/trivy-scan.sh" config - name: Scan candidate env: TRIVY_IGNORE_FILE: ${{ runner.temp }}/trivy-baseline-ignore.yaml TRIVY_REPORT_DIR: ${{ runner.temp }}/trivy-head run: | mkdir -p "$TRIVY_REPORT_DIR" tasks/scripts/trivy-scan.sh config - name: Reject new high or critical findings run: | tasks/scripts/trivy-scan.sh gate-config-diff \ "$RUNNER_TEMP/trivy-base" "$RUNNER_TEMP/trivy-head" - name: Upload reports if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: trivy-changes-${{ github.run_id }} path: | ${{ runner.temp }}/trivy-base ${{ runner.temp }}/trivy-head if-no-files-found: ignore retention-days: 14 result: name: OpenShell / Trivy Changes needs: [changes, scan] if: always() runs-on: ubuntu-latest steps: - name: Check scan result env: CHANGES_RESULT: ${{ needs.changes.result }} SHOULD_RUN: ${{ needs.changes.outputs.should_run }} SCAN_RESULT: ${{ needs.scan.result }} run: | set -euo pipefail if [ "$CHANGES_RESULT" != "success" ]; then echo "::error::Change detection concluded $CHANGES_RESULT." exit 1 fi if [ "$SHOULD_RUN" = "true" ] && [ "$SCAN_RESULT" != "success" ]; then echo "::error::Trivy scan concluded $SCAN_RESULT." exit 1 fi if [ "$SHOULD_RUN" != "true" ]; then echo "No Helm or Dockerfile changes to scan." fi