Skip to content

Minimally scope permissions in GitHub Actions workflows - #1248

Open
desrosj wants to merge 4 commits into
mainfrom
add/scoped-workflow-permissions
Open

Minimally scope permissions in GitHub Actions workflows#1248
desrosj wants to merge 4 commits into
mainfrom
add/scoped-workflow-permissions

Conversation

@desrosj

@desrosj desrosj commented May 14, 2026

Copy link
Copy Markdown
Member

This updates the GitHub Actions workflow files to:

  • Grant minimally-scoped permissions to each job to adhere to the principle of least privilege
  • Specify a timeout on each job to prevent runaway processes consuming too many minutes (the default is 360)

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

Field Value
Workflows scanned 22 (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)
Branch add/scoped-workflow-permissions (already existed — commits appended locally)
Permissions commit 3c5ce2e2
Timeouts commit dce59de3

Top-level permissions: {}

Category Entry
Workflows that were missing the top-level permissions: {} directive (added in this run): playground-preview.yml
Workflows that were missing the top-level permissions: {} directive (added in this run): codecoverage-main.yml
Workflows that were missing the top-level permissions: {} directive (added in this run): codecoverage-cleanup.yml

Job-level permissions: additions

Workflow file Summary Job Permissions / notes
delete-release.yml 1 job was missing an explicit job-level permissions directive delete permissions: {} [3]
cloudflare-clear-cache.yml 1 job was missing an explicit job-level permissions directive clear-cache permissions: {} [3]
performance-cron.yml 1 job was missing an explicit job-level permissions directive visit-site permissions: {} [3]
codecoverage-main.yml 1 job was missing an explicit job-level permissions directive get-repo-name permissions: {} [3]
upload-artifact-on-push.yml 1 job was missing scoped permissions for PR commenting and artifact upload build pull-requests: write, actions: write (in addition to existing contents: read) [3]
playground-preview.yml 1 job was missing actions scopes needed for artifact maintenance around Pages deploy playground-preview actions: read, actions: write (plus aligned inline comments on existing scopes) [2]
playground-cleanup.yml 1 job was missing artifact download scope for actions/download-artifact with github-token cleanup-preview actions: read (plus aligned inline comments on existing scopes) [3]
deploy-and-test.yml 1 job was missing artifact upload scope deploy actions: write (plus aligned inline comment on existing contents: read) [3]
playwright-tests.yml 2 jobs were missing artifact scopes build actions: write [3]
playwright-tests.yml 2 jobs were missing artifact scopes test actions: write [2]
playwright-tests-beta.yml 1 job was missing artifact upload scope test actions: write [3]
playwright-matrix.yml 3 jobs needed permission tightening/expansion should-run-matrix permissions: {} (replacing unnecessary contents: read) [3]
playwright-matrix.yml 3 jobs needed permission tightening/expansion test actions: write [3]
playwright-matrix.yml 3 jobs needed permission tightening/expansion matrix-summary actions: read [3]

Permissions corrections (previously incorrect)

# Correction
1 satis-webhook.yml :: webhook: BEFORE contents: write -> AFTER permissions: {} -- peter-evans/repository-dispatch uses secrets.WEBHOOK_TOKEN, not GITHUB_TOKEN, and there is no checkout/GitHub API use with the workflow token -- [3]
2 codecoverage-main.yml :: (workflow default): BEFORE permissions: contents: read at workflow level -> AFTER top-level permissions: {} + job-specific grants on callable jobs -- aligns with required default-deny pattern and avoids workflow-wide implicit grants -- [3]
3 create-milestones.yml :: create-milestone: BEFORE issues: write + contents: read -> AFTER issues: write -- job does not checkout the repository; milestone creation uses the Issues API with GITHUB_TOKEN -- [3]
4 playwright-matrix.yml :: should-run-matrix: BEFORE contents: read -> AFTER permissions: {} -- job reads only workflow event inputs/outputs in bash; no actions/checkout and no GitHub API calls -- [3]

timeout-minutes additions

Workflow Job Minutes / action Rationale
upload-asset-on-release.yml build 45 release packaging, dependency install, zip creation, and gh release upload routinely need more than a few minutes; caps runaway builds without changing any pre-existing timeouts elsewhere.
upload-artifact-on-push.yml build 45 mirrors the release build workload minus release upload; prevents stuck dependency/build steps from burning minutes indefinitely.
satis-webhook.yml webhook 10 webhook dispatch is short; a small cap catches hung runners or network stalls quickly.
playground-preview.yml playground-preview 45 Pages deploy + plugin build can be slow; generous but bounded limit for a multi-step preview pipeline.
playground-cleanup.yml cleanup-preview 30 artifact download, repack, and Pages redeploy can vary; a moderate cap limits wedged deploy steps.
deploy-and-test.yml deploy 60 SCP/SSH deploy plus Cypress smoke test polling needs headroom beyond typical lint jobs.
lint.yml phpcs 30 PHPCS over a plugin-sized tree and Composer setup fits comfortably under a 30-minute ceiling.
eslint.yml ESLint 30 npm ci + JS lint is usually fast; 30 minutes bounds rare hangs.
workflow-lint.yml lint 30 reusable workflow scan should complete quickly; cap prevents indefinite runs on parser/tool failures.
codecoverage-main.yml get-repo-name 5 single echo step; minimal cap.
codecoverage-main.yml codecoverage 120 reusable coverage matrix + Pages commit can be long-running; uses a higher ceiling without modifying any pre-existing timeouts in other workflows.
codecoverage-cleanup.yml get-merged-pr-commits 10 gh api commit listing should be quick; prevents stuck API calls from running unbounded.
codecoverage-cleanup.yml cleanup-on-merge 30 reusable cleanup touching gh-pages history may take meaningful time.
codecoverage-cleanup.yml cleanup-on-branch-delete 30 same rationale as merge cleanup; branch-delete pruning can be non-trivial.
codecoverage-cleanup.yml cleanup-scheduled 30 scheduled pruning/squash operations warrant the same guardrail as other cleanup callers.
cloudflare-clear-cache.yml clear-cache 10 single purge request should finish fast; tight cap.
delete-release.yml delete 10 single Cloudflare purge step; tight cap.
performance-cron.yml visit-site 15 login + a few admin requests with sleeps; small multi-minute budget with margin.
auto-translate.yml translate 60 reusable translation automation may branch/commit/PR; hour cap limits runaway scheduled/dispatch runs.
newfold-prepare-release.yml prep-release 60 release prep reusable workflow can touch branches/PRs; hour cap is a reasonable upper bound.
i18n-crowdin-upload.yml call-crowdin-upload-workflow 60 Crowdin upload reusable workflow may be slow; aligns with other i18n automation caps.
i18n-crowdin-download.yml call-crowdin-workflow 60 Crowdin download + PR flow can be slow; same rationale as upload caller.
create-milestones.yml create-milestone 10 single curl API call with date logic; should complete quickly.

Notes / blockers

# Note
1 Callable workflows in 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 typical GITHUB_TOKEN needs (Pages/PRs/contents) but should be confirmed against upstream workflow changes over time. [2]
2 Jobs using actions: write where the workflow both downloads and uploads artifacts (notably playwright-tests :: test) assume GitHub’s actions: write scope 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]
3 actions/configure-pages@v6 is invoked with secrets.GITHUB_TOKEN and enablement: true; if Pages enablement ever requires additional token scopes in your org/repo policy, revisit pages/administration requirements against current GitHub documentation. [2]

desrosj and others added 2 commits May 14, 2026 17:33
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@desrosj desrosj self-assigned this May 14, 2026
@desrosj
desrosj requested a review from Copilot May 14, 2026 23:37
@github-actions

github-actions Bot commented May 14, 2026

Copy link
Copy Markdown
Contributor

Latest Plugin Build

⬇️ Download build artifact: https://github.com/newfold-labs/wp-plugin-bluehost/actions/runs/26072293927/artifacts/7073554512
🗂️ Built from: add/scoped-workflow-permissions
📦 Commit: 8091ff2
📅 Date: 2026-05-19T02:23:50.890Z
🚀 Please download, test, and report any issues before merging. 🎉 This build includes the latest changes from this PR and was automatically generated by the GitHub Actions workflow. Any future commits will update this build.
Note: Artifacts expire after ~90 days

A WordPress Playground preview link will be added shortly by a separate workflow.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-minutes to every job (including reusable-workflow caller jobs).
  • Remove the Checkout and Download all Playwright matrix artifacts step header in playwright-matrix.yml's matrix-summary job.

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: writepermissions: {}; 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:
Comment on lines +20 to +21
actions: read # Required to list workflow run artifacts.
actions: write # Required to delete stale github-pages artifacts before upload.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you look into why actions permissions are needed at all and confirm the right access @copilot?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread .github/workflows/i18n-crowdin-upload.yml Outdated
Comment thread .github/workflows/i18n-crowdin-download.yml Outdated
Comment thread .github/workflows/auto-translate.yml Outdated
Comment thread .github/workflows/newfold-prepare-release.yml Outdated
Comment thread .github/workflows/codecoverage-main.yml Outdated
Comment on lines 47 to +71
@@ -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
Comment thread .github/workflows/workflow-lint.yml Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@desrosj

desrosj commented May 19, 2026

Copy link
Copy Markdown
Member Author

@copilot There are a lot of instances where actions: write is configured, but I don't think it's correct or required. Can you verify that this is required for each workflow and remove it if not?

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
@github-actions

Copy link
Copy Markdown
Contributor

Coverage: Base 26.12% → PR 26.12% (must not decrease).
✅ No decrease.

Code Coverage

project coverage report 26.12% @ 8091ff2

Copilot AI commented May 19, 2026

Copy link
Copy Markdown

@copilot There are a lot of instances where actions: write is configured, but I don't think it's correct or required. Can you verify that this is required for each workflow and remove it if not?

I audited every current actions: write grant on this branch and confirmed each one is required by the job’s behavior, so no removals were needed. They’re all tied to either actions/upload-artifact usage (requires write) or explicit artifact deletion via the Actions API in playground-preview. Verified against current HEAD c596065.

@circlecube
circlecube marked this pull request as ready for review August 12, 2026 16:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants