From b48decd3db4c774ff065e4d9ecb46961382b8ec4 Mon Sep 17 00:00:00 2001 From: S2D2 Date: Mon, 2 Nov 2026 02:22:18 +0000 Subject: [PATCH 1/4] ci: add report-only osv-scanner and zizmor security scan Adds .github/workflows/security-scan.yml running osv-scanner (pinned, checksum-verified release binary) and zizmor (pinned) on pull requests, pushes to main, weekly and on demand. Findings go to the job summary and never fail the job. gitleaks already runs in ci.yaml and all actions are already SHA-pinned, so neither is repeated here. Co-Authored-By: Claude Opus 5.5 Co-authored-by: Guillaume Lebedel <10488548+glebedel@users.noreply.github.com> --- .github/workflows/security-scan.yml | 89 +++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 .github/workflows/security-scan.yml diff --git a/.github/workflows/security-scan.yml b/.github/workflows/security-scan.yml new file mode 100644 index 0000000..29bc756 --- /dev/null +++ b/.github/workflows/security-scan.yml @@ -0,0 +1,89 @@ +name: Security scan + +# Report-only: findings are written to the job summary and never fail the job. +# Tighten to blocking once the existing backlog is cleared. +# Secret scanning already runs in ci.yaml (gitleaks job), so it is not repeated here. + +on: + pull_request: + push: + branches: [main] + schedule: + - cron: "23 5 * * 1" + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: security-scan-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +env: + OSV_SCANNER_VERSION: "2.7.0" + ZIZMOR_VERSION: "1.31.1" + +jobs: + osv-scanner: + name: Dependency scan (osv-scanner) + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Install osv-scanner + run: | + set -euo pipefail + tool_dir="$RUNNER_TEMP/osv-scanner" + mkdir -p "$tool_dir" + cd "$tool_dir" + base="https://github.com/google/osv-scanner/releases/download/v${OSV_SCANNER_VERSION}" + curl -fsSL --retry 3 -o osv-scanner_linux_amd64 "$base/osv-scanner_linux_amd64" + curl -fsSL --retry 3 -o osv-scanner_SHA256SUMS "$base/osv-scanner_SHA256SUMS" + grep -F " osv-scanner_linux_amd64" osv-scanner_SHA256SUMS | sha256sum -c - + install -m 0755 osv-scanner_linux_amd64 osv-scanner + rm osv-scanner_linux_amd64 + echo "$tool_dir" >> "$GITHUB_PATH" + + - name: Scan lockfiles + run: | + set -uo pipefail + report="$RUNNER_TEMP/osv.md" + osv-scanner scan source -r --format markdown . > "$report" || true + { + echo "## osv-scanner" + echo + if [ -s "$report" ]; then + head -c 900000 "$report" + else + echo "No report produced (no supported lockfiles, or the scan did not complete)." + fi + } >> "$GITHUB_STEP_SUMMARY" + + zizmor: + name: Workflow scan (zizmor) + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Audit workflows and local actions + env: + GH_TOKEN: ${{ github.token }} + run: | + set -uo pipefail + report="$RUNNER_TEMP/zizmor.txt" + pipx run "zizmor==${ZIZMOR_VERSION}" --format plain .github > "$report" 2>&1 || true + { + echo "## zizmor" + echo + echo '```text' + head -c 900000 "$report" + echo '```' + } >> "$GITHUB_STEP_SUMMARY" From 9167904924238cab107199ff9d5bc87738caa57a Mon Sep 17 00:00:00 2001 From: S2D2 Date: Mon, 2 Nov 2026 02:25:06 +0000 Subject: [PATCH 2/4] ci: drop redundant cleanup in osv-scanner install Co-Authored-By: Claude Opus 5.5 --- .github/workflows/security-scan.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/security-scan.yml b/.github/workflows/security-scan.yml index 29bc756..7449e59 100644 --- a/.github/workflows/security-scan.yml +++ b/.github/workflows/security-scan.yml @@ -45,7 +45,6 @@ jobs: curl -fsSL --retry 3 -o osv-scanner_SHA256SUMS "$base/osv-scanner_SHA256SUMS" grep -F " osv-scanner_linux_amd64" osv-scanner_SHA256SUMS | sha256sum -c - install -m 0755 osv-scanner_linux_amd64 osv-scanner - rm osv-scanner_linux_amd64 echo "$tool_dir" >> "$GITHUB_PATH" - name: Scan lockfiles From ad7ef406d46b88ac1fbb69c65bcb29c53b0db600 Mon Sep 17 00:00:00 2001 From: S2D2 Date: Mon, 2 Nov 2026 02:31:14 +0000 Subject: [PATCH 3/4] ci: rename security scan to .yaml and fail when a scan does not complete Findings still never fail the job. osv-scanner exit codes 127 and above and any zizmor error (run with --no-exit-codes) now fail the job instead of being reported as a clean run. Co-Authored-By: Claude Opus 5.5 Co-authored-by: Guillaume Lebedel <10488548+glebedel@users.noreply.github.com> --- .../{security-scan.yml => security-scan.yaml} | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) rename .github/workflows/{security-scan.yml => security-scan.yaml} (77%) diff --git a/.github/workflows/security-scan.yml b/.github/workflows/security-scan.yaml similarity index 77% rename from .github/workflows/security-scan.yml rename to .github/workflows/security-scan.yaml index 7449e59..2cd0fb8 100644 --- a/.github/workflows/security-scan.yml +++ b/.github/workflows/security-scan.yaml @@ -1,6 +1,7 @@ name: Security scan # Report-only: findings are written to the job summary and never fail the job. +# A scan that does not complete fails its job, so a broken scan is not reported as clean. # Tighten to blocking once the existing backlog is cleared. # Secret scanning already runs in ci.yaml (gitleaks job), so it is not repeated here. @@ -51,16 +52,19 @@ jobs: run: | set -uo pipefail report="$RUNNER_TEMP/osv.md" - osv-scanner scan source -r --format markdown . > "$report" || true + status=0 + osv-scanner scan source -r --format markdown . > "$report" || status=$? { echo "## osv-scanner" echo - if [ -s "$report" ]; then - head -c 900000 "$report" + # Exit 0: nothing found. Exit 1: vulnerabilities found. 127 and above: the scan did not complete. + if [ "$status" -ge 127 ]; then + echo "The scan did not complete (osv-scanner exit code $status). See the step log." else - echo "No report produced (no supported lockfiles, or the scan did not complete)." + head -c 900000 "$report" fi } >> "$GITHUB_STEP_SUMMARY" + [ "$status" -lt 127 ] || exit "$status" zizmor: name: Workflow scan (zizmor) @@ -78,11 +82,16 @@ jobs: run: | set -uo pipefail report="$RUNNER_TEMP/zizmor.txt" - pipx run "zizmor==${ZIZMOR_VERSION}" --format plain .github > "$report" 2>&1 || true + status=0 + pipx run "zizmor==${ZIZMOR_VERSION}" --no-exit-codes --format plain .github > "$report" 2>&1 || status=$? { echo "## zizmor" echo + if [ "$status" -ne 0 ]; then + echo "The scan did not complete (exit code $status). See the step log." + fi echo '```text' head -c 900000 "$report" echo '```' } >> "$GITHUB_STEP_SUMMARY" + exit "$status" From 868dac7a7559efe1c70ccd6152ccdbd0cc6c891e Mon Sep 17 00:00:00 2001 From: S2D2 Date: Mon, 2 Nov 2026 02:39:18 +0000 Subject: [PATCH 4/4] ci: pin the osv-scanner hash and fail only when a scanner fails The osv-scanner SHA-256 is pinned in the workflow instead of read from the release manifest. osv-scanner exit codes 0, 1 and 128 pass; any other code fails the job. zizmor runs under set -e with --no-exit-codes, so findings pass and tool errors fail. Co-Authored-By: Claude Opus 5.5 Co-authored-by: Guillaume Lebedel <10488548+glebedel@users.noreply.github.com> --- .github/workflows/security-scan.yaml | 30 ++++++++++++---------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/.github/workflows/security-scan.yaml b/.github/workflows/security-scan.yaml index 2cd0fb8..10f21ac 100644 --- a/.github/workflows/security-scan.yaml +++ b/.github/workflows/security-scan.yaml @@ -1,7 +1,7 @@ name: Security scan # Report-only: findings are written to the job summary and never fail the job. -# A scan that does not complete fails its job, so a broken scan is not reported as clean. +# A scanner that fails to run fails its job, so a broken scan is not reported as clean. # Tighten to blocking once the existing backlog is cleared. # Secret scanning already runs in ci.yaml (gitleaks job), so it is not repeated here. @@ -22,6 +22,8 @@ concurrency: env: OSV_SCANNER_VERSION: "2.7.0" + # Bump the version and the hash together. + OSV_SCANNER_SHA256: 160c6253e2922e9e0a6a9771c5c4c40a4b3c6090b34a3cdd5e80c96cc2e4218b ZIZMOR_VERSION: "1.31.1" jobs: @@ -43,8 +45,7 @@ jobs: cd "$tool_dir" base="https://github.com/google/osv-scanner/releases/download/v${OSV_SCANNER_VERSION}" curl -fsSL --retry 3 -o osv-scanner_linux_amd64 "$base/osv-scanner_linux_amd64" - curl -fsSL --retry 3 -o osv-scanner_SHA256SUMS "$base/osv-scanner_SHA256SUMS" - grep -F " osv-scanner_linux_amd64" osv-scanner_SHA256SUMS | sha256sum -c - + echo "${OSV_SCANNER_SHA256} osv-scanner_linux_amd64" | sha256sum -c - install -m 0755 osv-scanner_linux_amd64 osv-scanner echo "$tool_dir" >> "$GITHUB_PATH" @@ -57,14 +58,14 @@ jobs: { echo "## osv-scanner" echo - # Exit 0: nothing found. Exit 1: vulnerabilities found. 127 and above: the scan did not complete. - if [ "$status" -ge 127 ]; then - echo "The scan did not complete (osv-scanner exit code $status). See the step log." - else - head -c 900000 "$report" - fi + # Exit 0: nothing found. 1: vulnerabilities found. 128: no packages found. Anything else: the scan failed. + case "$status" in + 0 | 1) head -c 900000 "$report" ;; + 128) echo "No packages found to scan." ;; + *) echo "osv-scanner failed with exit code $status. See the step log." ;; + esac } >> "$GITHUB_STEP_SUMMARY" - [ "$status" -lt 127 ] || exit "$status" + case "$status" in 0 | 1 | 128) ;; *) exit "$status" ;; esac zizmor: name: Workflow scan (zizmor) @@ -80,18 +81,13 @@ jobs: env: GH_TOKEN: ${{ github.token }} run: | - set -uo pipefail + set -euo pipefail report="$RUNNER_TEMP/zizmor.txt" - status=0 - pipx run "zizmor==${ZIZMOR_VERSION}" --no-exit-codes --format plain .github > "$report" 2>&1 || status=$? + pipx run "zizmor==${ZIZMOR_VERSION}" --no-exit-codes --format plain .github > "$report" { echo "## zizmor" echo - if [ "$status" -ne 0 ]; then - echo "The scan did not complete (exit code $status). See the step log." - fi echo '```text' head -c 900000 "$report" echo '```' } >> "$GITHUB_STEP_SUMMARY" - exit "$status"