ci: make path-filtered required gates always report (unblock stuck PR… #542
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # SPDX-License-Identifier: MPL-2.0 | |
| # Copyright (c) 2026 Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk> | |
| # | |
| # Prevention workflow - runs OpenSSF Scorecard and fails on low scores. | |
| # | |
| # Split into TWO jobs to satisfy the OSSF Scorecard webapp's workflow | |
| # restriction: | |
| # "scorecard job must only have steps with `uses`" | |
| # (https://github.com/ossf/scorecard-action#workflow-restrictions). | |
| # | |
| # When the publishing job contains a `run:` step, the webapp rejects | |
| # the signed-result POST with HTTP 400 "workflow verification failed". | |
| # That regression had this workflow `failure`-state ever since | |
| # `publish_results: true` was added — the score *was* computed, but | |
| # the webapp publish step failed and propagated to job-level failure. | |
| # | |
| # Job 1 (`scorecard`): uses-only steps. Computes the score, publishes | |
| # signed results to the OSSF webapp, uploads SARIF to GitHub code | |
| # scanning, and saves results.sarif as an artifact for Job 2. | |
| # | |
| # Job 2 (`enforce-min-score`): downloads the artifact and runs the | |
| # minimum-score gate. May contain `run:` steps freely — it does not | |
| # call scorecard-action. | |
| # | |
| # Sister fix landed in hyperpolymath/ephapax#264 (2026-06-01); this | |
| # PR mirrors it so the two repos stay in sync. | |
| name: OpenSSF Scorecard Enforcer | |
| on: | |
| push: | |
| branches: [main] | |
| schedule: | |
| - cron: '0 6 * * 1' # Weekly on Monday | |
| workflow_dispatch: | |
| # Estate guardrail: cancel superseded runs so re-pushes / rebased PR | |
| # updates do not pile up queued runs against the shared account-wide | |
| # Actions concurrency pool. Applied only to read-only check workflows | |
| # (no publish/mutation), so cancelling a superseded run is always safe. | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: read | |
| jobs: | |
| scorecard: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| security-events: write | |
| id-token: write # For OIDC | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - name: Run Scorecard | |
| uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 | |
| with: | |
| results_file: results.sarif | |
| results_format: sarif | |
| publish_results: true | |
| - name: Upload SARIF | |
| uses: github/codeql-action/upload-sarif@c6f931105cb2c34c8f901cc885ba1e2e259cf745 # v4 | |
| with: | |
| sarif_file: results.sarif | |
| - name: Upload results artifact for min-score gate | |
| uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 | |
| with: | |
| name: scorecard-results | |
| path: results.sarif | |
| retention-days: 7 | |
| enforce-min-score: | |
| needs: scorecard | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Download results artifact | |
| uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4.1.9 | |
| with: | |
| name: scorecard-results | |
| - name: Check minimum score | |
| run: | | |
| # Parse score from SARIF 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 | |
| check-critical: | |
| runs-on: ubuntu-latest | |
| 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 |