ci: resolve CodeQL code-scanning alerts in workflows - #1825
Conversation
Clears all 27 open code-scanning alerts. Both classes are in GitHub Actions workflows; no Fortran or toolchain source is touched. missing-workflow-permissions (26 alerts, medium) No workflow declared a `permissions:` block, so every job inherited the repository-default GITHUB_TOKEN scope. None of the flagged jobs write to the repo through GITHUB_TOKEN -- the three that do publish use their own secrets (DOC_PUSH_URL, TAP_REPO_TOKEN, CODECOV_TOKEN) -- so a workflow-level `contents: read` default is sufficient everywhere. untrusted-checkout/high (1 alert, high) claude-code-review.yml runs privileged (pull_request_target and issue_comment: it holds CLAUDE_CODE_OAUTH_TOKEN plus pull-requests and issues write) and fetched fork commits into that workspace via `git fetch origin pull/<n>/head`. That step was dead code: nothing read FETCH_HEAD, and the pr_head_ref output was written but never consumed. The review already sources its diff and per-file context through gh API calls that treat fork content as data rather than as checked-out code, so the step and the unused output are removed instead of sandboxed. No behavior change. Claude-Session: https://claude.ai/code/session_01XPqfEaUBG7ZaZVzeMWnKHd
There was a problem hiding this comment.
🟡 Changes recommended
Several workflows that run dorny/paths-filter@v4 will likely fail on PR events because pull-requests: read is not granted when permissions is set to only contents: read.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR addresses GitHub CodeQL code-scanning alerts in GitHub Actions workflows by explicitly declaring least-privilege GITHUB_TOKEN permissions and by removing an unsafe, unused PR-head fetch from the privileged Claude review workflow.
Changes:
- Add explicit workflow-level
permissions: contents: readdefaults across CI workflows to avoid inheriting broader repo/org defaults. - Remove dead-code PR-head fetch (and an unused output) from the privileged
claude-code-review.ymlworkflow, keeping fork content as “data” viaghAPI calls.
File summaries
| File | Description |
|---|---|
| .github/workflows/test.yml | Adds explicit workflow-level token permissions (currently too restrictive for paths-filter; see comments). |
| .github/workflows/test-toolchain-compat.yml | Adds explicit workflow-level token permissions. |
| .github/workflows/spelling.yml | Adds explicit workflow-level token permissions. |
| .github/workflows/pmd.yml | Adds explicit workflow-level token permissions. |
| .github/workflows/lint-toolchain.yml | Adds explicit workflow-level token permissions. |
| .github/workflows/homebrew.yml | Adds explicit workflow-level token permissions. |
| .github/workflows/homebrew-release.yml | Adds explicit workflow-level token permissions. |
| .github/workflows/fp-stability.yml | Adds explicit workflow-level token permissions (currently too restrictive for paths-filter; see comments). |
| .github/workflows/formatting.yml | Adds explicit workflow-level token permissions. |
| .github/workflows/docs.yml | Adds explicit workflow-level token permissions. |
| .github/workflows/coverage.yml | Adds explicit workflow-level token permissions (currently too restrictive for paths-filter; see comments). |
| .github/workflows/coverage-health.yml | Adds explicit workflow-level token permissions. |
| .github/workflows/convergence.yml | Adds explicit workflow-level token permissions (currently too restrictive for paths-filter; see comments). |
| .github/workflows/cleanliness.yml | Adds explicit workflow-level token permissions (currently too restrictive for paths-filter; see comments). |
| .github/workflows/claude-code-review.yml | Removes unused PR-head resolution/fetch, avoiding untrusted checkout patterns in a privileged job. |
| .github/workflows/bench.yml | Adds explicit workflow-level token permissions (currently too restrictive for paths-filter; see comments). |
Review details
- Files reviewed: 16/16 changed files
- Comments generated: 6
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Declaring a `permissions:` block sets every unlisted scope to none, which
dropped `pull-requests` for the six workflows whose file-changes job runs
dorny/paths-filter. That action defaults to `token: ${{ github.token }}`
and reads the PR's changed-file list via pulls.listFiles on pull_request
events, so it needs pull-requests: read. test.yml also passes
`list-files: shell`, which depends on that list directly.
Applied to exactly the six paths-filter users: bench, cleanliness,
convergence, coverage, fp-stability, test. The other nine workflows
reference no GitHub API beyond contents and stay at contents: read.
Claude-Session: https://claude.ai/code/session_01XPqfEaUBG7ZaZVzeMWnKHd
|
Addressed in 5f4be21 — Copilot's finding was correct, and its list of six files was exactly right. Declaring a Those six now declare: permissions:
contents: read
pull-requests: readI re-audited the other nine while I was in there, and they stay at
Verified 1:1: the set of workflows using One note that reinforces the |
CodeQL flagged the `nvhpc` job this PR adds for missing permissions. The job itself is fine -- it checks out and pulls a container, nothing more -- but this branch is based on master, which has no `permissions:` block, so every job in test.yml still inherits the repository default. Add the workflow-level block here rather than relying on #1825 landing first, so this PR clears the alert on its own in either merge order. The block is byte-identical to the one #1825 adds to this file, so the two merge without conflict (verified with git merge-tree). Claude-Session: https://claude.ai/code/session_01XPqfEaUBG7ZaZVzeMWnKHd
Granting pull-requests: read at the workflow level handed it to every job in these six files, including the multi-hour NVHPC container lanes and the self-hosted Frontier and Phoenix runs, none of which touch the PR API. Only file-changes needs it: it is the sole job running dorny/paths-filter, it finishes in about ten seconds, and it holds no other privilege. Move the grant there and return the workflow default to contents: read. Job-level permissions replace the workflow default outright rather than merging with it, so contents: read is restated alongside pull-requests: read in the job block; a bare pull-requests: read there would have revoked contents and broken the job's own checkout. Claude-Session: https://claude.ai/code/session_01XPqfEaUBG7ZaZVzeMWnKHd
Mirrors the same change on #1825 so the two branches keep an identical permissions region in test.yml and continue to merge in either order. The nvhpc job this PR adds needs only contents: read -- it checks out and pulls a container image, using no GitHub token -- so it now inherits exactly that rather than a workflow-wide pull-requests grant it has no use for. Same for the self-hosted Frontier and Phoenix lanes. Claude-Session: https://claude.ai/code/session_01XPqfEaUBG7ZaZVzeMWnKHd
|
Tightened the scoping in 84cf49a (and mirrored on #1826 so the two files stay identical). Previously Only jobs:
file-changes:
name: Detect File Changes
permissions:
contents: read
pull-requests: readThe subtlety worth recording, and the reason Effective result across the six workflows, verified by parsing each file and resolving job-level against workflow-level:
Applied to |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #1825 +/- ##
=======================================
Coverage 62.26% 62.26%
=======================================
Files 84 84
Lines 21558 21558
Branches 3195 3195
=======================================
Hits 13423 13423
Misses 5937 5937
Partials 2198 2198 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Clears all 27 open code-scanning alerts. Both classes live in GitHub Actions workflows — no Fortran or toolchain source is touched.
missing-workflow-permissions— 26 alerts, mediumNo workflow declared a
permissions:block, so every job inherited the repository-defaultGITHUB_TOKENscope. That's read-only under current settings, but it's settings-dependent and would silently become write if the org default ever changed.I checked what each flagged job actually does with the token: none of the 26 write to the repo via
GITHUB_TOKEN. The three jobs that do publish use their own secrets instead —docs.ymlclonessecrets.DOC_PUSH_URL,homebrew-release.ymlusessecrets.TAP_REPO_TOKEN,coverage.ymlusessecrets.CODECOV_TOKEN. So a workflow-level read default is sufficient everywhere:Applied to
bench,cleanliness,convergence,coverage-health,coverage,docs,formatting,fp-stability,homebrew-release,homebrew,lint-toolchain,pmd,spelling,test-toolchain-compat,test. All 21 workflows now declare permissions at workflow or job level, and every file still parses as YAML.untrusted-checkout/high— 1 alert, high (#57)claude-code-review.ymlruns privileged —pull_request_target+issue_comment, so it holdsCLAUDE_CODE_OAUTH_TOKENalong withpull-requests: writeandissues: write— and it fetched fork commits into that privileged workspace viagit fetch origin pull/<n>/head.That step turned out to be dead code: nothing read
FETCH_HEAD, and thepr_head_refoutput was written to$GITHUB_OUTPUTand never consumed. The review already sources its diff and per-file context throughghAPI calls that treat fork content as data rather than as checked-out code. So the step and the unused output are removed outright rather than sandboxed — the flagged pattern goes away with no behavior change.A comment on the remaining
actions/checkoutrecords why the base repo is the only thing checked out, so the fetch doesn't get reintroduced later.Follow-ups, not addressed here
Two things worth a separate look — neither is flagged by CodeQL and neither is fixed in this PR:
claude-code-review.yml. Claude reads an attacker-authored diff while running--dangerously-skip-permissionswithBashallowed, in a job holding write scopes.claude-code-actiongating on the triggering actor's write access is the main protection. Since the review comment is already posted by a separate step, the job could be split — review atcontents: read, publish atpull-requests: write— so the model's step holds no write token at all.Retracted — I checked and this was wrong. Onbench.yml'spull_request_reviewpath may be a functional bug.pull_request_review,actions/checkoutresolves to the PR merge ref (refs/remotes/pull/<n>/merge), not the base branch, soClone - PRclones the PR as intended. Verified against run logs, and confirmed end-to-end: PR bug: m_bubbles_EL: delete non-finite bubbles in the boundary enforcement #1772's approval at18:32:41Ztriggered a bench run 4 seconds later in which all six Phoenix/Frontier jobs ran for 34–175 minutes. No bug here.https://claude.ai/code/session_01XPqfEaUBG7ZaZVzeMWnKHd