diff --git a/.github/workflows/boj-build.yml b/.github/workflows/boj-build.yml index 20848c4..fe681a0 100644 --- a/.github/workflows/boj-build.yml +++ b/.github/workflows/boj-build.yml @@ -19,7 +19,11 @@ jobs: trigger-boj: runs-on: ubuntu-latest timeout-minutes: 15 - 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 6d68c35..5c33aa3 100644 --- a/.github/workflows/scorecard-enforcer.yml +++ b/.github/workflows/scorecard-enforcer.yml @@ -21,15 +21,18 @@ permissions: contents: read jobs: - # Scorecard publish job — must be isolated from run steps per OpenSSF guidance. - scorecard-publish: + # 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: 30 + timeout-minutes: 15 permissions: security-events: write id-token: write # For OIDC - contents: read - actions: read steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: @@ -47,11 +50,17 @@ jobs: with: sarif_file: results.sarif - # Score enforcement job — separate from publish to satisfy scorecard_publish_with_run_step rule. - scorecard-enforce: + # 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 - needs: scorecard-publish permissions: contents: read steps: @@ -59,17 +68,17 @@ jobs: with: persist-credentials: false - - name: Run Scorecard (score only, no publish) + - name: Run Scorecard (analysis only) uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 with: - results_file: results.sarif - results_format: sarif + 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"