# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 name: Workflow Security Reports on: pull_request: merge_group: types: [checks_requested] push: branches: [main] schedule: - cron: "17 6 * * 1" workflow_dispatch: workflow_call: inputs: candidate_ref: description: Optional pre-release tag to scan instead of the triggering revision default: "" type: string fail-on-findings: description: Fail on Zizmor HIGH findings; Actionlint remains informational default: false type: boolean secrets: CACHIX_AUTH_TOKEN: required: false permissions: contents: read defaults: run: shell: nix develop --command bash -euo pipefail {0} concurrency: group: workflow-security-${{ github.workflow }}-${{ inputs.candidate_ref || github.ref }} cancel-in-progress: true jobs: actionlint: name: Actionlint (informational) runs-on: ubuntu-latest permissions: contents: read security-events: write steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 id: checkout with: ref: ${{ inputs.candidate_ref && format('refs/tags/{0}', inputs.candidate_ref) || '' }} persist-credentials: false - name: Set up Nix uses: ./.github/actions/setup-nix with: prepare-shell: "true" cachix-auth-token: ${{ github.event_name != 'pull_request' && secrets.CACHIX_AUTH_TOKEN || '' }} - name: Run Actionlint run: | set -uo pipefail mkdir -p reports echo "::add-matcher::.github/actionlint-matcher.json" set +e actionlint -shellcheck= -pyflakes= 2>&1 | tee reports/actionlint.txt status=${PIPESTATUS[0]} echo "::remove-matcher owner=actionlint::" actionlint \ -shellcheck= \ -pyflakes= \ -format "$(cat .github/actionlint-sarif-template.txt)" \ > reports/actionlint.sarif sarif_status=$? set -e if [ "$status" -le 1 ] && [ "$sarif_status" -ne "$status" ]; then echo "::error::Actionlint could not produce SARIF (exit $sarif_status)." exit "$sarif_status" fi { echo "### Actionlint" echo } >> "$GITHUB_STEP_SUMMARY" case "$status" in 0) echo "No findings." >> "$GITHUB_STEP_SUMMARY" ;; 1) echo "::warning::Actionlint reported findings; this check is informational." echo "Findings were reported as annotations and do not fail CI." >> "$GITHUB_STEP_SUMMARY" ;; 2|3) echo "::error::Actionlint could not complete (exit $status)." echo "Actionlint failed to run correctly (exit $status)." >> "$GITHUB_STEP_SUMMARY" exit "$status" ;; *) echo "::error::Actionlint returned unexpected exit code $status." echo "Actionlint returned unexpected exit code $status." >> "$GITHUB_STEP_SUMMARY" exit "$status" ;; esac - name: Upload Actionlint SARIF to Code Scanning if: success() uses: github/codeql-action/upload-sarif@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 with: sarif_file: reports/actionlint.sarif ref: ${{ inputs.candidate_ref && format('refs/tags/{0}', inputs.candidate_ref) || '' }} sha: ${{ inputs.candidate_ref && steps.checkout.outputs.commit || '' }} category: actionlint - name: Upload Actionlint report if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: actionlint-${{ github.run_id }} path: | reports/actionlint.txt reports/actionlint.sarif if-no-files-found: ignore retention-days: 14 zizmor: name: ${{ inputs.fail-on-findings && 'Zizmor High report' || 'Zizmor High report (informational)' }} runs-on: ubuntu-latest permissions: contents: read security-events: write steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 id: checkout with: ref: ${{ inputs.candidate_ref && format('refs/tags/{0}', inputs.candidate_ref) || '' }} persist-credentials: false - name: Set up Nix uses: ./.github/actions/setup-nix with: prepare-shell: "true" cachix-auth-token: ${{ github.event_name != 'pull_request' && secrets.CACHIX_AUTH_TOKEN || '' }} - name: Run Zizmor id: findings env: FAIL_ON_FINDINGS: ${{ inputs.fail-on-findings || false }} run: | set -euo pipefail mkdir -p reports zizmor \ --offline \ --persona=regular \ --min-severity=high \ --no-exit-codes \ --format=json \ . > reports/zizmor-high.json zizmor \ --offline \ --persona=regular \ --min-severity=high \ --no-exit-codes \ --format=sarif \ . > reports/zizmor-high.sarif finding_count=$(jq ' [.[] | select(.ignored != true and .determinations.severity == "High")] | length ' reports/zizmor-high.json) echo "high_count=$finding_count" >> "$GITHUB_OUTPUT" { echo "### Zizmor high-severity report" echo echo "Zizmor has no critical severity; high is its maximum level." echo echo "Findings: $finding_count" if [ "$finding_count" -gt 0 ]; then echo jq -r \ 'group_by(.ident) | .[] | "- `\(.[0].ident)`: \(length)"' \ reports/zizmor-high.json echo echo "Fail on HIGH findings: $FAIL_ON_FINDINGS" fi } >> "$GITHUB_STEP_SUMMARY" if [ "$finding_count" -gt 0 ]; then echo "::warning::Zizmor reported $finding_count high-severity findings." fi - name: Upload Zizmor SARIF to Code Scanning if: success() uses: github/codeql-action/upload-sarif@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 with: sarif_file: reports/zizmor-high.sarif ref: ${{ inputs.candidate_ref && format('refs/tags/{0}', inputs.candidate_ref) || '' }} sha: ${{ inputs.candidate_ref && steps.checkout.outputs.commit || '' }} category: zizmor-high - name: Upload Zizmor reports if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: zizmor-high-${{ github.run_id }} path: | reports/zizmor-high.json reports/zizmor-high.sarif if-no-files-found: ignore retention-days: 14 - name: Enforce HIGH/CRITICAL threshold if: ${{ !cancelled() && steps.findings.outcome == 'success' }} env: HIGH_COUNT: ${{ steps.findings.outputs.high_count }} FAIL_ON_FINDINGS: ${{ inputs.fail-on-findings || false }} run: | if [ "$HIGH_COUNT" -gt 0 ]; then if [ "$FAIL_ON_FINDINGS" = "true" ]; then echo "::error::Zizmor reported $HIGH_COUNT HIGH findings." exit 1 fi echo "::warning::Zizmor reported $HIGH_COUNT HIGH findings; enforcement is disabled." fi