From f3aedaeb3adb957d5c44fff08cdac354d3b5585d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 9 Jul 2026 13:48:30 +0900 Subject: [PATCH] fix(ci): least-privilege top-level GITHUB_TOKEN permissions Scorecard Token-Permissions (CWE: excessive GITHUB_TOKEN scope) flagged top-level write permissions across four workflows. Set each workflow's top-level permissions to read-only (contents: read) and keep the required write scopes declared at the job level: - strix.yml: move id-token/statuses write + actions/models read to the strix job; top level now contents: read (fixes alert #43). - security-scan.yml: top level -> contents: read; all jobs already declare their own scopes (fixes alert #42). - osv-scanner-pr.yml: top level -> contents: read; osv-scan keeps its job-level security-events: write for SARIF upload (fixes alert #41). - pr-review-merge-scheduler.yml: add top-level contents: read; the scan-pr-queue job keeps its explicit write scopes (fixes alert #9). No effective permission of any functional job is reduced. --- .github/workflows/osv-scanner-pr.yml | 6 +++--- .github/workflows/pr-review-merge-scheduler.yml | 6 ++++++ .github/workflows/security-scan.yml | 4 ++-- .github/workflows/strix.yml | 14 ++++++++++---- 4 files changed, 21 insertions(+), 9 deletions(-) diff --git a/.github/workflows/osv-scanner-pr.yml b/.github/workflows/osv-scanner-pr.yml index 4d5c4475..07abaf07 100644 --- a/.github/workflows/osv-scanner-pr.yml +++ b/.github/workflows/osv-scanner-pr.yml @@ -13,11 +13,11 @@ concurrency: group: osv-scanner-pr-${{ github.event.pull_request.base.repo.full_name || github.repository }}-${{ github.event.pull_request.number }} cancel-in-progress: true +# Least-privilege top level: read-only. The osv-scan job below declares the +# security-events: write scope it needs to upload SARIF to code scanning +# (see github/codeql-action#2117). permissions: - # Upload SARIF to Security > Code Scanning. See github/codeql-action#2117. - actions: read contents: read - security-events: write jobs: cancel-closed-pr-runs: diff --git a/.github/workflows/pr-review-merge-scheduler.yml b/.github/workflows/pr-review-merge-scheduler.yml index a21f339d..69d1e18e 100644 --- a/.github/workflows/pr-review-merge-scheduler.yml +++ b/.github/workflows/pr-review-merge-scheduler.yml @@ -115,6 +115,12 @@ on: required: false default: "420" +# Least-privilege top level: read-only. scan-pr-queue declares the exact +# write scopes it needs (actions/contents/pull-requests write, id-token, +# checks read); cancel-closed-pr-runs only inherits contents: read. +permissions: + contents: read + concurrency: group: >- central-pr-review-merge-scheduler-${{ github.repository }}-${{ diff --git a/.github/workflows/security-scan.yml b/.github/workflows/security-scan.yml index 7a077b05..d31806f1 100644 --- a/.github/workflows/security-scan.yml +++ b/.github/workflows/security-scan.yml @@ -35,10 +35,10 @@ concurrency: group: security-scan-${{ github.event.pull_request.base.repo.full_name || github.repository }}-${{ github.event.pull_request.number }} cancel-in-progress: true +# Least-privilege top level: read-only. Every job below declares the exact +# write scope it needs (security-events: write for SARIF upload, etc.). 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 071927b7..ad0914a2 100644 --- a/.github/workflows/strix.yml +++ b/.github/workflows/strix.yml @@ -92,12 +92,12 @@ 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' }} +# Least-privilege top level: read-only. The scan job declares the write +# scopes it needs (id-token for OIDC token exchange, statuses for commit +# status, plus actions/models reads); publish-manual-pr-evidence-status +# declares its own. cancel-closed-pr-runs only inherits contents: read. permissions: - actions: read contents: read - id-token: write - models: read - statuses: write jobs: cancel-closed-pr-runs: @@ -117,6 +117,12 @@ jobs: # the time. Fail-closed: hitting the cap fails the run, never passes it. timeout-minutes: 60 runs-on: ubuntu-latest + permissions: + actions: read + contents: read + id-token: write + models: read + statuses: write env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true steps: