Minimally scope permissions in GitHub Actions workflows#549
Draft
desrosj wants to merge 2 commits into
Draft
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
This PR tightens GitHub Actions security posture by default-denying GITHUB_TOKEN permissions at the workflow level and explicitly granting only the minimal permissions needed per job, while also adding job timeouts to prevent runaway CI usage.
Changes:
- Added workflow-level
permissions: {}(default deny) across workflows and moved required permissions to job scope. - Tightened/annotated job-level permissions for jobs that call reusable workflows (e.g., translations, Crowdin, code coverage, prep-release).
- Added
timeout-minutesto every job to cap runtime.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| .github/workflows/satis-update.yml | Adds default-deny permissions and a job timeout for the webhook dispatch workflow. |
| .github/workflows/newfold-prep-release.yml | Adds job timeout and documents job-level write permissions needed by the reusable release workflow. |
| .github/workflows/lint.yml | Adds default-deny permissions, job-scoped permissions, and a timeout for PHPCS linting. |
| .github/workflows/i18n-crowdin-upload.yml | Adds default-deny permissions, job-scoped contents: read, and a timeout for the reusable Crowdin upload workflow. |
| .github/workflows/i18n-crowdin-download.yml | Moves elevated permissions off the workflow to job scope (default deny at workflow), and adds a timeout. |
| .github/workflows/codecoverage-main.yml | Default-denies workflow permissions, scopes job permissions, and adds timeouts for both jobs. |
| .github/workflows/brand-plugin-test-playwright.yml | Default-denies workflow permissions, scopes job permissions, and adds timeouts for setup and reusable Playwright test job. |
| .github/workflows/auto-translate.yml | Keeps default-deny at workflow level, documents job write permissions, and adds a job timeout. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| phpcs: | ||
| name: Run PHP Code Sniffer | ||
| runs-on: ubuntu-latest | ||
| permissions: |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This updates the GitHub Actions workflow files to:
Once this PR is merged, the Settings -> Actions -> Workflow permissions setting can be changed by a repo admin to "Read repository contents and packages permissions".
For more information, see PRESS11-470.
References
Use of AI
Cursor was used with (Claude Opus 4.7 and Composer 2.0 at varying points) to analyze the repository and make the initial changes.
When this PR is marked "ready for review" it means that I have manually reviewed all permissions and timeouts that were changed and made any necessary adjustments.
As a part of the analysis, the following summary was created:
Status
auto-translate.yml,brand-plugin-test-playwright.yml,codecoverage-main.yml,i18n-crowdin-download.yml,i18n-crowdin-upload.yml,lint.yml,newfold-prep-release.yml,satis-update.yml)**/wp-module-patterns/.github/workflows/*.yml— 8 YAML files matched; zero*.yamlfiles in.github/workflowsadd/scoped-workflow-permissionsnot created — no remediation applied (audit-only; no edits)Top-level
permissions: {}permissions: {}directive (added in this run):# Disable permissions …comment pair immediately followed bypermissions: {}immediately beforejobs:(some have no equivalent block; others place non-empty workflowpermissions:or omit the mandated comments):permissions: {}directive (added in this run):satis-update.ymlpermissions: {}directive (added in this run):lint.ymlpermissions: {}directive (added in this run):codecoverage-main.yml(workflow-levelcontents: readinstead of{}+ mandated comments beforejobs:)permissions: {}directive (added in this run):brand-plugin-test-playwright.yml(workflow-levelcontents: read; comments/{}pattern missing)permissions: {}directive (added in this run):i18n-crowdin-upload.ymlpermissions: {}directive (added in this run):i18n-crowdin-download.yml(workflow-levelcontents: write/pull-requests: writeinstead of{}+ mandated comments)permissions: {}directive (added in this run):auto-translate.yml(permissions: {}present but without the two mandated comment lines before it)newfold-prep-release.ymlJob-level
permissions:additionspermissions:block immediately afterruns-on:/uses:(or need tightening to least privilege with per-permission trailing comments as required by the audit policy):satis-update.yml: 1 job —webhookLikely
permissions: {}for the defaultGITHUB_TOKEN[3] — usespeter-evans/repository-dispatchwithsecrets.WEBHOOK_TOKEN(notgithub.token); noactions/checkoutstep.lint.yml: 1 job —phpcsAdd
contents: read[3] —actions/checkoutneeds clone access in a private repo.codecoverage-main.yml: 1 job —get-repo-nameAdd explicit
permissions: {}[3] — no checkout / no GitHub API use in shown steps.codecoverage-main.yml: jobcodecoveragealready declarescontents: writeandpull-requests: writebut without the required trailing inline comment on each key (should be brought in line with the audit’s “comment per permission” rule).brand-plugin-test-playwright.yml: 2 jobs —setup,bluehostsetup: likelypermissions: {}[3] — branch name echo only.bluehost: at minimumcontents: readfor typical reusable clone patterns [2] — exact needs depend onnewfold-labs/workflowsmodule-plugin-test-playwright.yml@main(not fully verified from this repo).i18n-crowdin-upload.yml: 1 job —call-crowdin-upload-workflowMissing job
permissions:entirely [2] — scope should be derived fromnewfold-labs/workflowsi18n-crowdin-upload.yml@main(Crowdin + any PR/push behavior).i18n-crowdin-download.yml: 1 job —call-crowdin-workflowMove workflow-level
contents: write/pull-requests: writeto this job and add per-permission trailing comments [2] — behavior depends on reusable workflow (not fully verified from this repo).auto-translate.yml: jobtranslatealready has write scopes but without per-permission trailing comments (should be aligned with the audit format).Permissions corrections (previously incorrect)
codecoverage-main.yml:: workflow: BEFOREpermissions: contents: read(top-level) -> AFTER top-levelpermissions: {}plus job-scoped permissions (withget-repo-nameexplicitcontents: readonly if later steps add checkout/API use) — reason: audit requires default-deny at workflow level and least privilege on jobs — [3]brand-plugin-test-playwright.yml:: workflow: BEFOREpermissions: contents: read(top-level) -> AFTER top-levelpermissions: {}plus job-level scoped permissions — reason: same — [3]i18n-crowdin-download.yml:: workflow: BEFOREcontents: write+pull-requests: writeat workflow level -> AFTER top-levelpermissions: {}and the same scopes on the job that calls the reusable workflow (with inline comments) — reason: workflow-wide elevation violates the audit’s default-deny pattern — [3]auto-translate.yml:: workflow: BEFOREpermissions: {}without mandated comment block -> AFTER two-line comment block +permissions: {}beforejobs:— reason: audit mandates the exact preamble — [3]newfold-prep-release.yml::prep-release: BEFOREcontents: write/pull-requests: writewithout per-line comments -> AFTER same keys with trailing comments (e.g. push branch, open PR) — reason: matches audit’s required comment style — [2]timeout-minutesadditionstimeout-minutes(repo-wide grep in.github/workflows: no matches). Suggested additions (not applied; pick values to match expected runtime — often 30 minutes):Notes / blockers
uses:reusable workflows (newfold-labs/workflows@main) should be validated against those reusable workflow definitions plus GitHub workflowpermissionsdocumentation; this audit infers scopes from naming and typical patterns where the callee was not fetched.lint.yml(technote-space/get-diff-action,shivammathur/setup-php,actions/cache) were not traced line-by-line for defaultGITHUB_TOKENusage beyondactions/checkout— if any step elevates implicitly, tighten job permissions accordingly after verification.satis-update.yml:secrets.WEBHOOK_TOKENis a PAT/application token to another repo; rotate/review secrecy separately fromGITHUB_TOKENhardening — still apply default workflowpermissions: {}+ explicit job scopes for completeness.