Minimally scope permissions in GitHub Actions workflows - #1248
Conversation
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Latest Plugin Build⬇️ Download build artifact: A WordPress Playground preview link will be added shortly by a separate workflow. |
There was a problem hiding this comment.
Pull request overview
This PR hardens all GitHub Actions workflows by switching to least-privilege permissions (top-level deny + per-job grants) and by adding job-level timeout-minutes caps to prevent runaway runs. It also documents each granted scope with an inline comment.
Changes:
- Add
permissions: {}at workflow scope (where missing) and replace broad/incorrect job-level grants with minimal, commented per-job permissions. - Add
timeout-minutesto every job (including reusable-workflow caller jobs). - Remove the
CheckoutandDownload all Playwright matrix artifactsstep header inplaywright-matrix.yml'smatrix-summaryjob.
Reviewed changes
Copilot reviewed 22 out of 22 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| .github/workflows/auto-translate.yml | Tightens permission comments and adds 60m timeout on reusable caller. |
| .github/workflows/cloudflare-clear-cache.yml | Adds permissions: {} and 10m timeout. |
| .github/workflows/codecoverage-cleanup.yml | Replaces workflow-level grant with permissions: {}; adds per-job permissions/timeouts. |
| .github/workflows/codecoverage-main.yml | Switches to top-level permissions: {}; adds per-job permissions and timeouts. |
| .github/workflows/create-milestones.yml | Drops unused contents: read; adds 10m timeout. |
| .github/workflows/delete-release.yml | Adds permissions: {} and 10m timeout. |
| .github/workflows/deploy-and-test.yml | Adds actions: write and 60m timeout. |
| .github/workflows/eslint.yml | Adds 30m timeout; documents contents: read. |
| .github/workflows/i18n-crowdin-download.yml | Adds 60m timeout on reusable caller; comments perms. |
| .github/workflows/i18n-crowdin-upload.yml | Adds 60m timeout on reusable caller; comments perms. |
| .github/workflows/lint.yml | Adds 30m timeout; documents contents: read. |
| .github/workflows/newfold-prepare-release.yml | Adds 60m timeout on reusable caller; comments perms. |
| .github/workflows/performance-cron.yml | Adds permissions: {} and 15m timeout. |
| .github/workflows/playground-cleanup.yml | Adds actions: read; adds 30m timeout. |
| .github/workflows/playground-preview.yml | Adds top-level permissions: {}; expands job perms (duplicate actions: key introduced); 45m timeout. |
| .github/workflows/playwright-matrix.yml | Switches should-run-matrix to permissions: {}; adds actions: write/read; removes Checkout & download-artifact step name in matrix-summary. |
| .github/workflows/playwright-tests-beta.yml | Adds actions: write for artifact upload. |
| .github/workflows/playwright-tests.yml | Adds actions: write to build and test jobs. |
| .github/workflows/satis-webhook.yml | Changes contents: write → permissions: {}; adds 10m timeout. |
| .github/workflows/upload-artifact-on-push.yml | Adds pull-requests: write, actions: write; 45m timeout. |
| .github/workflows/upload-asset-on-release.yml | Adds 45m timeout; documents contents: write. |
| .github/workflows/workflow-lint.yml | Adds 30m timeout on reusable caller; comments perms. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| contents: read # Required to clone the repo for the summary script. | ||
| actions: read # Required to download matrix Playwright artifacts from this workflow run. | ||
|
|
||
| steps: |
| actions: read # Required to list workflow run artifacts. | ||
| actions: write # Required to delete stale github-pages artifacts before upload. |
There was a problem hiding this comment.
Can you look into why actions permissions are needed at all and confirm the right access @copilot?
There was a problem hiding this comment.
I checked this flow end-to-end and actions is needed because the Cleanup previous github-pages artifacts for this run step calls github.rest.actions.listWorkflowRunArtifacts and github.rest.actions.deleteArtifact with GITHUB_TOKEN. That requires Actions scope, and actions: write is the minimal single grant here (it covers the read needed for listing). I updated this to one key (actions: write) in commit c596065.
| @@ -52,8 +56,9 @@ jobs: | |||
|
|
|||
| cleanup-on-branch-delete: | |||
| if: github.event_name == 'delete' && github.event.ref_type == 'branch' | |||
| timeout-minutes: 30 | |||
| permissions: | |||
| contents: write | |||
| contents: write # Required for reusable workflow to prune coverage output on gh-pages. | |||
| uses: newfold-labs/workflows/.github/workflows/reusable-codecoverage-cleanup.yml@main | |||
| with: | |||
| shas: '' | |||
| @@ -63,8 +68,9 @@ jobs: | |||
|
|
|||
| cleanup-scheduled: | |||
| if: github.event_name == 'schedule' | |||
| timeout-minutes: 30 | |||
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
|
@copilot There are a lot of instances where |
| cleanup-on-merge: | ||
| needs: get-merged-pr-commits | ||
| if: always() && needs.get-merged-pr-commits.result == 'success' | ||
| timeout-minutes: 30 |
|
|
||
| cleanup-on-branch-delete: | ||
| if: github.event_name == 'delete' && github.event.ref_type == 'branch' | ||
| timeout-minutes: 30 |
|
|
||
| cleanup-scheduled: | ||
| if: github.event_name == 'schedule' | ||
| timeout-minutes: 30 |
Agent-Logs-Url: https://github.com/newfold-labs/wp-plugin-bluehost/sessions/abb9be9a-a275-41fb-8e4f-d119f0d46a43 Co-authored-by: desrosj <359867+desrosj@users.noreply.github.com>
|
Coverage: Base 26.12% → PR 26.12% (must not decrease). |
I audited every current |
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,cloudflare-clear-cache.yml,codecoverage-cleanup.yml,codecoverage-main.yml,create-milestones.yml,delete-release.yml,deploy-and-test.yml,eslint.yml,i18n-crowdin-download.yml,i18n-crowdin-upload.yml,lint.yml,newfold-prepare-release.yml,performance-cron.yml,playground-cleanup.yml,playground-preview.yml,playwright-matrix.yml,playwright-tests-beta.yml,playwright-tests.yml,satis-webhook.yml,upload-artifact-on-push.yml,upload-asset-on-release.yml,workflow-lint.yml)add/scoped-workflow-permissions(already existed — commits appended locally)3c5ce2e2dce59de3Top-level
permissions: {}permissions: {}directive (added in this run):playground-preview.ymlpermissions: {}directive (added in this run):codecoverage-main.ymlpermissions: {}directive (added in this run):codecoverage-cleanup.ymlJob-level
permissions:additionspermissionsdirectivepermissions: {}[3]permissionsdirectivepermissions: {}[3]permissionsdirectivepermissions: {}[3]permissionsdirectivepermissions: {}[3]pull-requests: write,actions: write(in addition to existingcontents: read) [3]actionsscopes needed for artifact maintenance around Pages deployactions: read,actions: write(plus aligned inline comments on existing scopes) [2]actions/download-artifactwithgithub-tokenactions: read(plus aligned inline comments on existing scopes) [3]actions: write(plus aligned inline comment on existingcontents: read) [3]actions: write[3]actions: write[2]actions: write[3]permissions: {}(replacing unnecessarycontents: read) [3]actions: write[3]actions: read[3]Permissions corrections (previously incorrect)
satis-webhook.yml::webhook: BEFOREcontents: write-> AFTERpermissions: {}--peter-evans/repository-dispatchusessecrets.WEBHOOK_TOKEN, notGITHUB_TOKEN, and there is no checkout/GitHub API use with the workflow token -- [3]codecoverage-main.yml:: (workflow default): BEFOREpermissions: contents: readat workflow level -> AFTER top-levelpermissions: {}+ job-specific grants on callable jobs -- aligns with required default-deny pattern and avoids workflow-wide implicit grants -- [3]create-milestones.yml::create-milestone: BEFOREissues: write+contents: read-> AFTERissues: write-- job does not checkout the repository; milestone creation uses the Issues API withGITHUB_TOKEN-- [3]playwright-matrix.yml::should-run-matrix: BEFOREcontents: read-> AFTERpermissions: {}-- job reads only workflow event inputs/outputs in bash; noactions/checkoutand no GitHub API calls -- [3]timeout-minutesadditions45gh release uploadroutinely need more than a few minutes; caps runaway builds without changing any pre-existing timeouts elsewhere.45104530603030npm ci+ JS lint is usually fast; 30 minutes bounds rare hangs.30512010gh apicommit listing should be quick; prevents stuck API calls from running unbounded.30gh-pageshistory may take meaningful time.30301010156060606010curlAPI call with date logic; should complete quickly.Notes / blockers
newfold-labs/workflows(reusable-codecoverage*.yml,reusable-translations.yml,reusable-plugin-prep-release.yml, Crowdin callers,reusable-workflow-lint.yml) were not fully audited line-by-line; permissions on the caller jobs were chosen to match typicalGITHUB_TOKENneeds (Pages/PRs/contents) but should be confirmed against upstream workflow changes over time. [2]actions: writewhere the workflow both downloads and uploads artifacts (notablyplaywright-tests::test) assume GitHub’sactions: writescope is sufficient for artifact downloads in the same job; if GitHub ever enforces stricter separation, split permissions across jobs or verify with the Actions team/docs. [2]actions/configure-pages@v6is invoked withsecrets.GITHUB_TOKENandenablement: true; if Pages enablement ever requires additional token scopes in your org/repo policy, revisitpages/administrationrequirements against current GitHub documentation. [2]