From b5c3ae4de2d73f2977a2ac486cf0fe2e5e9e179e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 9 Jul 2026 01:30:43 +0900 Subject: [PATCH] fix(ci): tighten GITHUB_TOKEN to least privilege in workflows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Scorecard Token-Permissions findings on the org .github workflows. Each fix follows least privilege — the workflow-level token is read-only and write scopes live only on the jobs that need them. No functionality is removed; every write grant is preserved at job scope where it was already (or is now) declared. - strix.yml (alert #43): move top-level id-token:write + statuses:write onto the `strix` scan job (which had no explicit block and relied on the broad top-level token); workflow-level token is now read-only. The publish job keeps its own job-scoped writes. - security-scan.yml (alert #42): drop redundant top-level security-events:write — the osv-scan, trivy-fs and scorecard jobs each already declare it. - osv-scanner-pr.yml (alert #41): drop redundant top-level security-events:write — the osv-scan reusable-workflow job already declares it at job scope. - pr-review-merge-scheduler.yml (alert #9): add a least-privilege top-level `permissions: contents: read` default; scan-pr-queue keeps its own writes. - pr-review-fix-scheduler.yml (alert #8): add a least-privilege top-level `permissions: contents: read` default; dispatch-review-fixes keeps its own. Tests: full suite unchanged (171 pass; 3 pre-existing stale-contract failures present on base, unrelated to permissions). Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01RTAMs4bpSZS77Xe3RQjv9P --- .github/workflows/osv-scanner-pr.yml | 6 ++++-- .github/workflows/pr-review-fix-scheduler.yml | 6 ++++++ .github/workflows/pr-review-merge-scheduler.yml | 7 +++++++ .github/workflows/security-scan.yml | 5 ++++- .github/workflows/strix.yml | 14 ++++++++++++-- 5 files changed, 33 insertions(+), 5 deletions(-) diff --git a/.github/workflows/osv-scanner-pr.yml b/.github/workflows/osv-scanner-pr.yml index 4d5c4475f..756c7dff5 100644 --- a/.github/workflows/osv-scanner-pr.yml +++ b/.github/workflows/osv-scanner-pr.yml @@ -14,10 +14,12 @@ concurrency: cancel-in-progress: true permissions: - # Upload SARIF to Security > Code Scanning. See github/codeql-action#2117. + # Scorecard Token-Permissions (alert #41): keep the workflow-level token + # read-only. SARIF upload needs security-events:write, but the osv-scan job + # below already grants it at job scope, so it is redundant (and over-broad) + # here. actions: read contents: read - security-events: write jobs: cancel-closed-pr-runs: diff --git a/.github/workflows/pr-review-fix-scheduler.yml b/.github/workflows/pr-review-fix-scheduler.yml index f59db584e..e36d15edb 100644 --- a/.github/workflows/pr-review-fix-scheduler.yml +++ b/.github/workflows/pr-review-fix-scheduler.yml @@ -90,6 +90,12 @@ concurrency: group: central-pr-review-fix-scheduler-${{ inputs.target_repository || vars.PR_REVIEW_FIX_TARGET_REPOSITORY || github.repository }} cancel-in-progress: true +# Scorecard Token-Permissions (alert #8): declare a least-privilege default at +# the workflow level. The dispatch-review-fixes job declares its own elevated +# permissions block; the default token stays read-only. +permissions: + contents: read + jobs: dispatch-review-fixes: runs-on: ubuntu-latest diff --git a/.github/workflows/pr-review-merge-scheduler.yml b/.github/workflows/pr-review-merge-scheduler.yml index a21f339dd..3c54983bf 100644 --- a/.github/workflows/pr-review-merge-scheduler.yml +++ b/.github/workflows/pr-review-merge-scheduler.yml @@ -127,6 +127,13 @@ concurrency: github.ref }} cancel-in-progress: ${{ github.event_name == 'pull_request_target' || github.event_name == 'workflow_dispatch' }} +# Scorecard Token-Permissions (alert #9): declare a least-privilege default at +# the workflow level. The scan-pr-queue job that actually needs write access +# declares its own elevated permissions block; every other job (and the default +# token) stays read-only. +permissions: + contents: read + jobs: cancel-closed-pr-runs: if: github.event_name == 'pull_request_target' && github.event.action == 'closed' diff --git a/.github/workflows/security-scan.yml b/.github/workflows/security-scan.yml index 7a077b05c..18772e694 100644 --- a/.github/workflows/security-scan.yml +++ b/.github/workflows/security-scan.yml @@ -35,10 +35,13 @@ concurrency: group: security-scan-${{ github.event.pull_request.base.repo.full_name || github.repository }}-${{ github.event.pull_request.number }} cancel-in-progress: true +# Scorecard Token-Permissions (alert #42): workflow-level token stays +# read-only. Every job that uploads SARIF (osv-scan, trivy-fs, scorecard) +# already declares security-events:write at job scope, so granting it here as +# well is redundant and over-broad. permissions: actions: read contents: read - security-events: write jobs: cancel-closed-pr-runs: diff --git a/.github/workflows/strix.yml b/.github/workflows/strix.yml index 071927b74..5bb4f7ccf 100644 --- a/.github/workflows/strix.yml +++ b/.github/workflows/strix.yml @@ -92,12 +92,13 @@ concurrency: # commit. Closed PR events only cancel older runs for the same PR/head group. cancel-in-progress: ${{ github.event_name == 'pull_request_target' && github.event.action == 'closed' }} +# Scorecard Token-Permissions (alert #43): keep the workflow-level token +# read-only and grant the id-token/statuses writes only on the job that needs +# them (the strix scan job below and the publish-manual-pr-evidence-status job). permissions: actions: read contents: read - id-token: write models: read - statuses: write jobs: cancel-closed-pr-runs: @@ -117,6 +118,15 @@ jobs: # the time. Fail-closed: hitting the cap fails the run, never passes it. timeout-minutes: 60 runs-on: ubuntu-latest + # Least-privilege token scoped to this job (Scorecard alert #43): the scan + # exchanges an OIDC token (id-token) and posts a commit status + # (statuses:write); all other scopes stay read-only. + permissions: + actions: read + contents: read + id-token: write + models: read + statuses: write env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true steps: