diff --git a/.github/workflows/scorecard-enforcer.yml b/.github/workflows/scorecard-enforcer.yml index 874532e4..75e23854 100644 --- a/.github/workflows/scorecard-enforcer.yml +++ b/.github/workflows/scorecard-enforcer.yml @@ -1,4 +1,4 @@ -# SPDX-License-Identifier: PMPL-1.0-or-later +# SPDX-License-Identifier: MPL-2.0 # Prevention workflow - runs OpenSSF Scorecard and fails on low scores name: OpenSSF Scorecard Enforcer @@ -21,6 +21,16 @@ permissions: contents: read jobs: + # The OSSF Scorecard publish endpoint enforces a hard contract: the job that + # runs `ossf/scorecard-action` with `publish_results: true` must contain + # ONLY steps with `uses:` (no `run:` steps in the same job). If a `run:` + # step is present, the publish step fails with: + # "webapp: scorecard job must only have steps with uses" + # (49 estate repos hit this; see ROADMAP audit 2026-05-30.) + # + # Fix: split the threshold check into a downstream job that depends on + # `scorecard` and consumes the SARIF artifact. The `scorecard` job stays + # uses-only; `check-score` is the gating job that emits the error. scorecard: runs-on: ubuntu-latest permissions: @@ -36,29 +46,44 @@ jobs: with: results_file: results.sarif results_format: sarif - publish_results: false + publish_results: true - name: Upload SARIF uses: github/codeql-action/upload-sarif@c6f931105cb2c34c8f901cc885ba1e2e259cf745 # v4 with: sarif_file: results.sarif + - name: Persist SARIF for downstream score-gate job + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + with: + name: scorecard-results + path: results.sarif + retention-days: 1 + + check-score: + needs: scorecard + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Download SARIF from scorecard job + uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v5.0.0 + with: + name: scorecard-results + - name: Check minimum score run: | - # Parse overall score from SARIF. - # The scorecard-action SARIF uses a non-standard property path; - # fall back to the tool version string which embeds the score. - # Note: this step is informational only (enforcer score gating - # is handled by the OSSF webapp via scorecard.yml publish). - SCORE=$(jq -r ' - .runs[0].properties.metricResults[]? | - select(.id == "AggregateScore") | .value - ' results.sarif 2>/dev/null || echo "unknown") + SCORE=$(jq -r '.runs[0].tool.driver.properties.score // 0' results.sarif 2>/dev/null || echo "0") echo "OpenSSF Scorecard Score: $SCORE" - # Score check is advisory — do not fail the workflow here. - # Hard enforcement is via scorecard.yml + OSSF webapp badge. + # 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: