From ab942a092f37735cb473d8fc95c64f182a5ee79f Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 4 Jun 2026 00:07:03 +0000 Subject: [PATCH] fix(ci): keep scorecard-action jobs uses-only (split compute-score from gate) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR #359 fixed the enforcer's score-reading bug (read `.score` from JSON instead of the absent SARIF field) but reintroduced the job-structure bug #304 had deliberately removed: it put `ossf/scorecard-action` and a `run:` step in the same `check-score` job. OSSF's publish path requires scorecard-action jobs to be uses-only; hypatia flags this as `scorecard_publish_with_run_step` (high). Split the score path into two jobs: `compute-score` (uses-only — scorecard-action in JSON mode with publish_results:false, then upload-artifact) hands the JSON to `check-score` (download-artifact + the jq gate, no scorecard-action). Every scorecard-action job is now uses-only; MIN_SCORE=5 and the publish `scorecard` job are unchanged. No SPDX/licence edit. https://claude.ai/code/session_011xv3VLrqeXkpjXxUojKz82 --- .github/workflows/scorecard-enforcer.yml | 36 +++++++++++++++++++----- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/.github/workflows/scorecard-enforcer.yml b/.github/workflows/scorecard-enforcer.yml index db64bbe4..53bd4dd5 100644 --- a/.github/workflows/scorecard-enforcer.yml +++ b/.github/workflows/scorecard-enforcer.yml @@ -54,13 +54,16 @@ jobs: with: sarif_file: results.sarif - # Gate on the aggregate score. The score is NOT present in the SARIF output - # (the previous `jq '.runs[0].tool.driver.properties.score'` always returned - # null → 0 → this gate failed on every push regardless of the real posture). - # The aggregate score only exists in scorecard's JSON output, so run the - # action here with `results_format: json` (and `publish_results: false`, so - # this job needs no OIDC/id-token) and read `.score`. - check-score: + # Compute the aggregate score in its OWN uses-only job. The score is NOT in + # the SARIF output (`jq '.runs[0].tool.driver.properties.score'` always + # returned null → 0 → this gate failed on every push regardless of the real + # posture); it only exists in scorecard's JSON output. scorecard-action and a + # `run:` step must never share a job (OSSF publish contract — see #304, and + # hypatia `scorecard_publish_with_run_step`), so this job stays uses-only and + # hands the JSON to check-score via an artifact. `publish_results: false` + # means this run neither publishes nor needs OIDC (the `scorecard` job above + # owns publishing). + compute-score: timeout-minutes: 20 needs: scorecard runs-on: ubuntu-latest @@ -78,6 +81,25 @@ jobs: results_format: json publish_results: false + - name: Persist score JSON for the gate job + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + with: + name: scorecard-score-json + path: results.json + retention-days: 1 + + check-score: + timeout-minutes: 10 + needs: compute-score + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Download score JSON + uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v5.0.0 + with: + name: scorecard-score-json + - name: Check minimum score run: | SCORE=$(jq -r '.score // 0' results.json 2>/dev/null || echo "0")