diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index f35054e59..097b55dcc 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -10,12 +10,23 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }}${{ github.event_name == 'pull_request_review' && format('-review-{0}', github.run_id) || '' }} cancel-in-progress: true +# Least-privilege default: no job in this workflow writes to the repo. +permissions: + contents: read + jobs: file-changes: name: Detect File Changes if: > github.event_name != 'pull_request_review' || github.event.review.user.type != 'Bot' + # Job-level permissions replace the workflow default outright rather than + # merging with it, so contents must be restated here. paths-filter reads the + # PR's changed-file list via pulls.listFiles; this is the only job that needs + # it, so it is granted here instead of workflow-wide. + permissions: + contents: read + pull-requests: read runs-on: 'ubuntu-latest' outputs: checkall: ${{ steps.changes.outputs.checkall }} diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index 0bcc06dfb..d5fc7a32d 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -58,26 +58,17 @@ jobs: exit 1 fi - PR_HEAD_REF="$(gh pr view "$PR_NUMBER" --repo "${{ github.repository }}" --json headRefName --jq .headRefName)" - echo "pr_number=$PR_NUMBER" >> "$GITHUB_OUTPUT" - echo "pr_head_ref=$PR_HEAD_REF" >> "$GITHUB_OUTPUT" + # Base repo only. This job is privileged (pull_request_target / issue_comment: + # it holds secrets and a write-capable token), so the PR head is never checked + # out or fetched here. The diff and per-file context are pulled through the gh + # API in later steps and treated purely as data. - name: Checkout base repo uses: actions/checkout@v5 with: fetch-depth: 0 - - name: Fetch PR head - shell: bash - env: - PR_NUMBER: ${{ steps.mode.outputs.pr_number }} - run: | - set -euo pipefail - # Fetch the PR merge ref — works for both same-repo and fork PRs - # (fork branches don't exist on origin, but pull//head always does) - git fetch origin "pull/${PR_NUMBER}/head" - - name: Resolve review state id: state shell: bash diff --git a/.github/workflows/cleanliness.yml b/.github/workflows/cleanliness.yml index 8e7a1511f..dd1003042 100644 --- a/.github/workflows/cleanliness.yml +++ b/.github/workflows/cleanliness.yml @@ -10,9 +10,20 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +# Least-privilege default: no job in this workflow writes to the repo. +permissions: + contents: read + jobs: file-changes: name: Detect File Changes + # Job-level permissions replace the workflow default outright rather than + # merging with it, so contents must be restated here. paths-filter reads the + # PR's changed-file list via pulls.listFiles; this is the only job that needs + # it, so it is granted here instead of workflow-wide. + permissions: + contents: read + pull-requests: read runs-on: 'ubuntu-latest' outputs: checkall: ${{ steps.changes.outputs.checkall }} diff --git a/.github/workflows/convergence.yml b/.github/workflows/convergence.yml index 0acad5b59..e4dd8d373 100644 --- a/.github/workflows/convergence.yml +++ b/.github/workflows/convergence.yml @@ -10,9 +10,20 @@ on: env: OMPI_MCA_rmaps_base_oversubscribe: 1 +# Least-privilege default: no job in this workflow writes to the repo. +permissions: + contents: read + jobs: file-changes: name: Detect File Changes + # Job-level permissions replace the workflow default outright rather than + # merging with it, so contents must be restated here. paths-filter reads the + # PR's changed-file list via pulls.listFiles; this is the only job that needs + # it, so it is granted here instead of workflow-wide. + permissions: + contents: read + pull-requests: read runs-on: ubuntu-latest outputs: checkall: ${{ steps.changes.outputs.checkall }} diff --git a/.github/workflows/coverage-health.yml b/.github/workflows/coverage-health.yml index e50d30027..16f501d36 100644 --- a/.github/workflows/coverage-health.yml +++ b/.github/workflows/coverage-health.yml @@ -4,6 +4,10 @@ on: schedule: - cron: '0 7 * * *' # daily; loud if the refresh stopped working workflow_dispatch: +# Least-privilege default: no job in this workflow writes to the repo. +permissions: + contents: read + jobs: health: if: github.repository == 'MFlowCode/MFC' diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index f331b5808..357deef54 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -11,9 +11,20 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +# Least-privilege default: no job in this workflow writes to the repo. +permissions: + contents: read + jobs: file-changes: name: Detect File Changes + # Job-level permissions replace the workflow default outright rather than + # merging with it, so contents must be restated here. paths-filter reads the + # PR's changed-file list via pulls.listFiles; this is the only job that needs + # it, so it is granted here instead of workflow-wide. + permissions: + contents: read + pull-requests: read runs-on: 'ubuntu-latest' outputs: checkall: ${{ steps.changes.outputs.checkall }} diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 212db44b9..341269c31 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -8,6 +8,10 @@ on: branches: [master] pull_request: +# Least-privilege default: no job in this workflow writes to the repo. +permissions: + contents: read + jobs: build: name: Build & Verify diff --git a/.github/workflows/formatting.yml b/.github/workflows/formatting.yml index 0689b891f..580948f81 100644 --- a/.github/workflows/formatting.yml +++ b/.github/workflows/formatting.yml @@ -6,6 +6,10 @@ on: pull_request: workflow_dispatch: +# Least-privilege default: no job in this workflow writes to the repo. +permissions: + contents: read + jobs: docs: name: Formatting diff --git a/.github/workflows/fp-stability.yml b/.github/workflows/fp-stability.yml index 1bf8313d9..68a6ac4ea 100644 --- a/.github/workflows/fp-stability.yml +++ b/.github/workflows/fp-stability.yml @@ -35,9 +35,20 @@ on: types: [opened, synchronize, reopened, ready_for_review] workflow_dispatch: +# Least-privilege default: no job in this workflow writes to the repo. +permissions: + contents: read + jobs: file-changes: name: Detect File Changes + # Job-level permissions replace the workflow default outright rather than + # merging with it, so contents must be restated here. paths-filter reads the + # PR's changed-file list via pulls.listFiles; this is the only job that needs + # it, so it is granted here instead of workflow-wide. + permissions: + contents: read + pull-requests: read runs-on: ubuntu-latest outputs: checkall: ${{ steps.changes.outputs.checkall }} diff --git a/.github/workflows/homebrew-release.yml b/.github/workflows/homebrew-release.yml index a9f99e204..fe616e96c 100644 --- a/.github/workflows/homebrew-release.yml +++ b/.github/workflows/homebrew-release.yml @@ -21,6 +21,10 @@ on: type: boolean default: false +# Least-privilege default: no job in this workflow writes to the repo. +permissions: + contents: read + jobs: update-homebrew-tap: name: Update homebrew-mfc tap diff --git a/.github/workflows/homebrew.yml b/.github/workflows/homebrew.yml index 7511486be..df17d218b 100644 --- a/.github/workflows/homebrew.yml +++ b/.github/workflows/homebrew.yml @@ -11,6 +11,10 @@ on: - '.github/workflows/homebrew.yml' workflow_dispatch: +# Least-privilege default: no job in this workflow writes to the repo. +permissions: + contents: read + jobs: # Fast smoke tests that run before expensive operations smoke-test: diff --git a/.github/workflows/lint-toolchain.yml b/.github/workflows/lint-toolchain.yml index 2d7431c24..13f6fa825 100644 --- a/.github/workflows/lint-toolchain.yml +++ b/.github/workflows/lint-toolchain.yml @@ -6,6 +6,10 @@ on: pull_request: workflow_dispatch: +# Least-privilege default: no job in this workflow writes to the repo. +permissions: + contents: read + jobs: lint-toolchain: name: Lint Toolchain diff --git a/.github/workflows/pmd.yml b/.github/workflows/pmd.yml index d44027017..b87302823 100644 --- a/.github/workflows/pmd.yml +++ b/.github/workflows/pmd.yml @@ -6,6 +6,10 @@ on: pull_request: workflow_dispatch: +# Least-privilege default: no job in this workflow writes to the repo. +permissions: + contents: read + jobs: pmd: name: PMD diff --git a/.github/workflows/spelling.yml b/.github/workflows/spelling.yml index ca2e5d801..b2e035216 100644 --- a/.github/workflows/spelling.yml +++ b/.github/workflows/spelling.yml @@ -6,6 +6,10 @@ on: pull_request: workflow_dispatch: +# Least-privilege default: no job in this workflow writes to the repo. +permissions: + contents: read + jobs: run: name: Spell Check diff --git a/.github/workflows/test-toolchain-compat.yml b/.github/workflows/test-toolchain-compat.yml index e02409176..2a6051953 100644 --- a/.github/workflows/test-toolchain-compat.yml +++ b/.github/workflows/test-toolchain-compat.yml @@ -6,6 +6,10 @@ on: pull_request: workflow_dispatch: +# Least-privilege default: no job in this workflow writes to the repo. +permissions: + contents: read + jobs: test-toolchain: name: "Python ${{ matrix.python-version }}" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8c57bb559..1119bed64 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,6 +12,10 @@ concurrency: group: ${{ github.workflow }}-${{ github.event_name == 'push' && github.sha || github.ref }} cancel-in-progress: ${{ github.event_name != 'push' }} +# Least-privilege default: no job in this workflow writes to the repo. +permissions: + contents: read + jobs: lint-gate: name: Lint Gate @@ -50,6 +54,13 @@ jobs: file-changes: name: Detect File Changes + # Job-level permissions replace the workflow default outright rather than + # merging with it, so contents must be restated here. paths-filter reads the + # PR's changed-file list via pulls.listFiles; this is the only job that needs + # it, so it is granted here instead of workflow-wide. + permissions: + contents: read + pull-requests: read runs-on: 'ubuntu-latest' outputs: checkall: ${{ steps.changes.outputs.checkall }}