Skip to content

Minimally scope permissions in GitHub Actions workflows#49

Draft
desrosj wants to merge 2 commits into
mainfrom
add/scoped-workflow-permissions
Draft

Minimally scope permissions in GitHub Actions workflows#49
desrosj wants to merge 2 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 3 (brand-plugin-test.yml, codecoverage-main.yml, satis-webhook.yml)
Branch add/scoped-workflow-permissions (created)
Permissions commit b8d83a0
Timeouts commit 0b1323d

Top-level permissions: {}

Category Entry
Workflows that were missing the top-level permissions: {} directive (added in this run): brand-plugin-test.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): satis-webhook.yml

Job-level permissions: additions

Workflow file Summary Job Permissions / notes
satis-webhook.yml 1 job was missing a scoped permissions: directive webhook {} [3]
codecoverage-main.yml 1 job was missing a scoped permissions: directive codecoverage contents: write, pull-requests: write [3]
brand-plugin-test.yml 6 jobs were missing a scoped permissions: directive setup {} [3]
brand-plugin-test.yml 6 jobs were missing a scoped permissions: directive Bluehost Build and Test contents: read [2]
brand-plugin-test.yml 6 jobs were missing a scoped permissions: directive HostGator Build and Test contents: read [2]
brand-plugin-test.yml 6 jobs were missing a scoped permissions: directive Web.com Build and Test contents: read [2]
brand-plugin-test.yml 6 jobs were missing a scoped permissions: directive Crazy Domains Build and Test contents: read [2]
brand-plugin-test.yml 6 jobs were missing a scoped permissions: directive Mojo Build and Test contents: read [2]

Permissions corrections (previously incorrect)

No pre-existing configured permissions: were identified as incorrect.

timeout-minutes additions

Workflow Job Minutes / action Rationale
satis-webhook.yml webhook 30 Webhook packaging and dispatch are quick; thirty minutes guards against stalled runners without constraining legitimate runs.
codecoverage-main.yml codecoverage 60 MySQL setup, Composer, PHP/Codeception matrices, and merging coverage routinely need more wall time than a short smoke workflow.
brand-plugin-test.yml setup 10 The job only echoes branch outputs; ten minutes avoids runaway executions while exceeding normal runtime by a wide margin.
brand-plugin-test.yml Bluehost Build and Test 45 The reusable workflow runs Composer/npm, builds, wp-env, and Cypress suites, which can be slow on cold caches.
brand-plugin-test.yml HostGator Build and Test 45 Same rationale as Bluehost (module-plugin-test reusable workflow workload).
brand-plugin-test.yml Web.com Build and Test 45 Same rationale as Bluehost (module-plugin-test reusable workflow workload).
brand-plugin-test.yml Crazy Domains Build and Test 45 Same rationale as Bluehost (module-plugin-test reusable workflow workload).
brand-plugin-test.yml Mojo Build and Test 45 Same rationale as Bluehost (module-plugin-test reusable workflow workload).

Notes / blockers

# Note
1 Reusable newfold-labs/workflows/.github/workflows/module-plugin-test.yml@main already scopes its inner job to contents: read; caller jobs mirror that ceiling so the granting token cannot elevate beyond what the reusable workflow declares. Confirm in live runs whether contents: read continues to suffice for artifacts/caching behaviour under your organization’s Actions defaults (actions/cache/upload-artifact); if workflows fail post-hardening, update module-plugin-test.yml upstream to declare any additional minimal scopes rather than widening callers alone.

desrosj and others added 2 commits May 14, 2026 17:29
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:34

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Hardens GitHub Actions workflows by applying the principle of least privilege (top-level permissions: {} and job-scoped permissions) and adding per-job timeout-minutes to bound runtime.

Changes:

  • Add top-level permissions: {} to all three workflows and explicit job-level permissions for every job.
  • Add timeout-minutes to every job (10/30/45/60 minutes depending on workload).
  • For brand-plugin-test jobs that call a reusable workflow, set contents: read to mirror the downstream workflow's scope.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
.github/workflows/satis-webhook.yml Adds top-level permissions: {}, empty job-level permissions, and a 30-minute timeout for the webhook job.
.github/workflows/codecoverage-main.yml Adds top-level permissions: {}, scopes the codecoverage job to contents: write + pull-requests: write, and adds a 60-minute timeout.
.github/workflows/brand-plugin-test.yml Adds top-level permissions: {}, scopes each job (setup + 5 brand jobs), and adds timeout-minutes to each — including on jobs that use a reusable workflow via uses:, which is not a supported key in that context.
Comments suppressed due to low confidence (4)

.github/workflows/brand-plugin-test.yml:51

  • timeout-minutes is not supported on a job that uses uses: to call a reusable workflow. This line will cause workflow validation to fail. Remove it (or set the timeout inside the reusable workflow).
    timeout-minutes: 45

.github/workflows/brand-plugin-test.yml:64

  • timeout-minutes is not supported on a job that calls a reusable workflow via uses:. This will cause workflow validation to fail.
    timeout-minutes: 45

.github/workflows/brand-plugin-test.yml:77

  • timeout-minutes is not supported on a job that calls a reusable workflow via uses:. This will cause workflow validation to fail.
    timeout-minutes: 45

.github/workflows/brand-plugin-test.yml:90

  • timeout-minutes is not supported on a job that calls a reusable workflow via uses:. This will cause workflow validation to fail.
    timeout-minutes: 45

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

uses: newfold-labs/workflows/.github/workflows/module-plugin-test.yml@main
permissions:
contents: read # Matches downstream reusable workflow; required for checkout/composer/GitHub-hosted actions using GITHUB_TOKEN on private repos
timeout-minutes: 45
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.

2 participants