From a36660d83ff1b37fc31beabd62a68ab1650feb6b Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 14 Jun 2026 16:50:42 +0000 Subject: [PATCH] ci: fix boj-build secrets-in-if + Scorecard enforcer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit boj-build.yml: removed the job-level `if:` referencing secrets.BOJ_SERVER_URL. The `secrets` context is not available in `if:` — referencing it invalidates the whole workflow. The trigger step already shell-guards on an empty BOJ_URL and exits 0, so an unconfigured repo stays a fast no-op (honouring the documented secret-or-var configuration). scorecard-enforcer.yml: the scorecard job invoked ossf/scorecard-action with publish_results: true AND a run: step in the same job, which OSSF rejects with HTTP 400 ("scorecard job must only have steps with `uses`"), failing the run. Split into an uses-only publish job + an unprivileged JSON score-gate (reads top-level `.score`; the SARIF aggregate path is absent so the old gate always read 0 and failed < 5). Least-privilege contents:read, timeouts, SHA pins. https://claude.ai/code/session_01VwbFNQJw23tW8tqM7utWku --- .github/workflows/boj-build.yml | 6 +++- .github/workflows/scorecard-enforcer.yml | 37 ++++++++++++++++++++++-- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/.github/workflows/boj-build.yml b/.github/workflows/boj-build.yml index f933154..1440671 100644 --- a/.github/workflows/boj-build.yml +++ b/.github/workflows/boj-build.yml @@ -18,7 +18,11 @@ permissions: jobs: trigger-boj: runs-on: ubuntu-latest - if: ${{ vars.BOJ_SERVER_URL != '' || secrets.BOJ_SERVER_URL != '' }} + # No job-level enablement gate: the `secrets` context is not available in + # `if:` (referencing it invalidates the whole workflow), and gating on + # `vars` alone would silently drop a secret-only configuration. The + # "Trigger BoJ Server" step shell-guards on an empty BOJ_URL and exits 0, + # so an unconfigured repo is a fast no-op regardless of secret-or-var. steps: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 diff --git a/.github/workflows/scorecard-enforcer.yml b/.github/workflows/scorecard-enforcer.yml index 6933b78..5c33aa3 100644 --- a/.github/workflows/scorecard-enforcer.yml +++ b/.github/workflows/scorecard-enforcer.yml @@ -21,8 +21,15 @@ permissions: contents: read jobs: + # Publish job. The OSSF attestation flow requires that a job invoking + # ossf/scorecard-action with publish_results: true contain ONLY `uses:` + # steps (no `run:`). A `run:` step in this job makes the OSSF publish + # endpoint reject the upload with HTTP 400 ("scorecard job must only have + # steps with `uses`"), failing the whole workflow. Score enforcement is + # therefore split into the separate unprivileged `score-gate` job below. scorecard: runs-on: ubuntu-latest + timeout-minutes: 15 permissions: security-events: write id-token: write # For OIDC @@ -43,10 +50,35 @@ jobs: with: sarif_file: results.sarif + # Unprivileged enforcement gate. Re-derives the score read-only (no + # publish, no id-token), so it may legally contain a `run:` step. + # NOTE: uses JSON output deliberately. The SARIF format does NOT carry the + # aggregate score at .runs[0].tool.driver.properties.score (it is absent + # there, so a SARIF-based gate reads the `// 0` fallback and ALWAYS fails + # the < MIN_SCORE check); the JSON format exposes the aggregate at the + # top-level `.score`. publish_results is false here and does not affect the + # computed score, so enforcement behaviour is unchanged. + score-gate: + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: read + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Run Scorecard (analysis only) + uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 + with: + results_file: results.json + results_format: json + publish_results: false + - 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") + # JSON output carries the aggregate score at the top-level `.score`. + SCORE=$(jq -r '.score // 0' results.json 2>/dev/null || echo "0") echo "OpenSSF Scorecard Score: $SCORE" @@ -61,6 +93,7 @@ jobs: # Check specific high-priority items check-critical: runs-on: ubuntu-latest + timeout-minutes: 15 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2