From a834a19b6d820e483948ec1ea1e6428a2a87edbd Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 20 Jun 2026 04:20:49 +0000 Subject: [PATCH] ci: extract scorecard run steps into composite actions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Hypatia `scorecard_publish_with_run_step` rule is file-level: any run: step in a workflow that publishes OpenSSF Scorecard results trips it, so the earlier job-split did not clear it. Move all run logic out of the scorecard-publishing workflow into local composite actions, leaving scorecard-enforcer.yml with no run: steps at all: - .github/actions/scorecard-gate — the minimum-score gate (consumes the SARIF). - .github/actions/repo-security-checks — SECURITY.md + unpinned-action checks. The publish job (which holds the OIDC id-token) and the whole workflow file now contain only `uses:` steps; the score-gate and check-critical jobs invoke the composite actions after checkout. Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01AqMopxUsgu78rg5fhWBUkk --- .../actions/repo-security-checks/action.yml | 22 +++++++++ .github/actions/scorecard-gate/action.yml | 26 +++++++++++ .github/workflows/scorecard-enforcer.yml | 45 ++++++------------- 3 files changed, 62 insertions(+), 31 deletions(-) create mode 100644 .github/actions/repo-security-checks/action.yml create mode 100644 .github/actions/scorecard-gate/action.yml diff --git a/.github/actions/repo-security-checks/action.yml b/.github/actions/repo-security-checks/action.yml new file mode 100644 index 0000000..be2d895 --- /dev/null +++ b/.github/actions/repo-security-checks/action.yml @@ -0,0 +1,22 @@ +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) Jonathan D.A. Jewell +name: Repository Security Checks +description: Assert SECURITY.md exists and warn on unpinned actions in workflows. +runs: + using: composite + steps: + - name: Check SECURITY.md exists + shell: bash + run: | + if [ ! -f "SECURITY.md" ]; then + echo "::error::SECURITY.md is required" + exit 1 + fi + - name: Check for pinned dependencies + shell: bash + run: | + unpinned=$(grep -r "uses:.*@v[0-9]" .github/workflows/*.yml 2>/dev/null | grep -v "#" | head -5 || true) + if [ -n "$unpinned" ]; then + echo "::warning::Found unpinned actions:" + echo "$unpinned" + fi diff --git a/.github/actions/scorecard-gate/action.yml b/.github/actions/scorecard-gate/action.yml new file mode 100644 index 0000000..84f83cb --- /dev/null +++ b/.github/actions/scorecard-gate/action.yml @@ -0,0 +1,26 @@ +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) Jonathan D.A. Jewell +name: Scorecard Score Gate +description: Fail when the OpenSSF Scorecard score is below the minimum. +inputs: + sarif: + description: Path to the Scorecard SARIF results file. + required: false + default: results.sarif + min-score: + description: Minimum acceptable score (0-10). + required: false + default: "5" +runs: + using: composite + steps: + - name: Check minimum score + shell: bash + run: | + SCORE=$(jq -r '.runs[0].tool.driver.properties.score // 0' "${{ inputs.sarif }}" 2>/dev/null || echo "0") + echo "OpenSSF Scorecard Score: $SCORE" + MIN_SCORE="${{ inputs.min-score }}" + if [ "$(echo "$SCORE < $MIN_SCORE" | bc -l)" = "1" ]; then + echo "::error::Scorecard score $SCORE is below minimum $MIN_SCORE" + exit 1 + fi diff --git a/.github/workflows/scorecard-enforcer.yml b/.github/workflows/scorecard-enforcer.yml index 9f1ea5c..b695f41 100644 --- a/.github/workflows/scorecard-enforcer.yml +++ b/.github/workflows/scorecard-enforcer.yml @@ -43,8 +43,9 @@ jobs: name: scorecard-sarif path: results.sarif retention-days: 1 - # The score gate runs in a separate job so the publish job (which holds the - # OIDC id-token) contains no custom run steps. + # The score gate runs the check via a local composite action so this + # scorecard-publishing workflow file contains no run: steps (the publish job + # holds the OIDC id-token; run logic lives in .github/actions/*). score-gate: needs: scorecard runs-on: ubuntu-latest @@ -52,41 +53,23 @@ jobs: permissions: contents: read steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Download results artifact uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: scorecard-sarif - - name: Check minimum score - run: | - # Parse score from results - SCORE=$(jq -r '.runs[0].tool.driver.properties.score // 0' results.sarif 2>/dev/null || echo "0") - - echo "OpenSSF Scorecard Score: $SCORE" - - # Minimum acceptable score (0-10 scale) - MIN_SCORE=5 - - if [ "$(echo "$SCORE < $MIN_SCORE" | bc -l)" = "1" ]; then - echo "::error::Scorecard score $SCORE is below minimum $MIN_SCORE" - exit 1 - fi - # Check specific high-priority items + - name: Gate on minimum score + uses: ./.github/actions/scorecard-gate + with: + sarif: results.sarif + min-score: "5" + # Check specific high-priority items (run logic in a local composite action). check-critical: runs-on: ubuntu-latest timeout-minutes: 15 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - name: Check SECURITY.md exists - run: | - if [ ! -f "SECURITY.md" ]; then - echo "::error::SECURITY.md is required" - exit 1 - fi - - name: Check for pinned dependencies - run: | - # Check workflows for unpinned actions - unpinned=$(grep -r "uses:.*@v[0-9]" .github/workflows/*.yml 2>/dev/null | grep -v "#" | head -5 || true) - if [ -n "$unpinned" ]; then - echo "::warning::Found unpinned actions:" - echo "$unpinned" - fi + - name: Repository security checks + uses: ./.github/actions/repo-security-checks