Join our Newsletter — 33% off our NHI Course
Home› Guides› How to Restrict GITHUB_TOKEN Permissions in Workflows: Read-Only…
Guide Secrets Management

How to Restrict GITHUB_TOKEN Permissions in Workflows: Read-Only Defaults, Per-Job Scopes and an Audit You Can Repeat

← All guides
By NHI Mgmt Group, edited by Lalit Choda Updated 11 November 2026 11 min read
On this page

Every GitHub Actions job receives a GITHUB_TOKEN, and any step in that job, including third-party actions, can use whatever that token is allowed to do. This guide covers one task: moving an organisation from broad token permissions to read-only defaults with explicit per-job scopes, without breaking builds, and then auditing that it stays that way. It is written for platform engineers and organisation owners on GitHub.com who already know the basics of Actions. For the wider credential picture, including OIDC, GitHub Apps and runners, see the GitHub and GitHub Actions Security Guide; this page goes deeper on the token alone.

This is an independent guide from NHI Mgmt Group, based on GitHub's public documentation as of November 2026. It is not sponsored or reviewed by GitHub. Product features change, so confirm settings against GitHub's current documentation.

Key takeaways

  • Two layers set the token's power. The default at enterprise, organisation or repository level, then the workflow's permissions key, workflow level first and job level after.
  • Listing one scope sets the rest to none. GitHub's workflow syntax states that once any permission is specified, all unspecified ones become none, which is what makes per-job blocks safe.
  • Set the default to read, then grant write per job. The restricted default gives read access to contents and packages only; jobs that publish, comment or deploy ask for exactly what they need.
  • Fork and trigger rules matter as much as scopes. pull_request_target receives a read and write token even from a public fork, so treat those workflows as privileged.
  • Audit by API and by file. Default settings are readable through the REST API, and workflow files can be scanned for missing or broad permissions blocks on a schedule.

When you need this

  • Organisation default is still read and write: older organisations and repositories may have been created when the permissive default was common. Every workflow without a permissions key inherits it.
  • Third-party actions in use: GitHub notes that an action can read the token through the github.token context even when the workflow does not pass it explicitly. The token's scope is the blast radius of any action you run.
  • Audit or certification finding: a least privilege finding on CI credentials, or a low OpenSSF Scorecard Token-Permissions result, which the Scorecard project rates as a high-risk check.
  • After a workflow incident: tightening scopes is a standard hardening step once a compromised action or injection has been contained. See the CI/CD Pipeline Compromise Playbook.

Before you start

  • Access: organisation owner for the organisation default; repository admin for repository settings. Reading the defaults by API needs the Administration permission (read) on a fine-grained token or GitHub App, or admin:org (organisation) or repo (repository) on a classic token.
  • Inventory workflows: list every .github/workflows/*.yml file across repositories, and note which have a top-level permissions key, which set it per job, and which have none.
  • Know what writes: for each workflow without a permissions key, work out what it changes in GitHub: pushes, tags, releases, comments, labels, packages, Pages, deployments, code scanning uploads. That list becomes its job-level grants.
  • Pick a pilot: a handful of busy repositories with different workflow styles, so failures show up early.

Step-by-step

1. Add explicit permissions to workflows first

Start in the files, because a workflow with its own permissions key behaves the same whatever the default is. That lets you flip the default later with little breakage.

  1. Top of every workflow: add permissions: {} (no access) or permissions: contents: read. Scorecard's remediation advice is read-all or contents: read at the top level; contents: read is the narrower of the two.
  2. Per job: add jobs.<job_id>.permissions with only the scopes that job uses. Job-level settings apply after the workflow-level ones, so a release job can have contents: write while the test job stays read-only.
  3. Remember the reset: because unlisted scopes become none, a job that sets issues: write loses contents: read unless you list it too. Jobs that check out code need contents: read in their own block.
  4. Avoid write-all: it grants write on every scope that accepts it. If a job seems to need it, split the job.
Job does thisGrantNotes
Checks out code, builds and testscontents: readEnough for most jobs
Pushes commits, creates tags or releasescontents: writeKeep in a separate release job
Comments on or labels pull requestspull-requests: writeAdd contents: read if it also checks out
Comments on or labels issuesissues: write
Publishes to GitHub Packages or the container registrypackages: writeRestricted default already gives packages read
Uploads code scanning (SARIF) resultssecurity-events: write
Publishes GitHub Pagespages: write and id-token: writeGitHub's Pages docs give both as the minimum for the deploy job
Requests an OIDC token for cloud accessid-token: writeAccepts only write or none; see the GitHub Actions OIDC Federation Hardening Guide
Creates build provenance attestationsattestations: writeGitHub's attestation docs pair it with id-token: write and contents: read, plus packages: write for container images
Manages runs, caches or artifacts by APIactions: writeRarely needed; review carefully

The full list of scopes in GitHub's workflow syntax reference as of November 2026 is: actions, artifact-metadata, attestations, checks, code-quality, contents, copilot-requests, deployments, discussions, id-token, issues, packages, pages, pull-requests, security-events, statuses and vulnerability-alerts. Some accept only certain values: vulnerability-alerts takes read or none, while id-token and copilot-requests take write or none.

2. Set the restricted default

  1. Organisation: in the organisation's Actions settings, under Workflow permissions, select the read-only option for contents and packages. GitHub states that when an organisation selects the more restrictive default, its repositories inherit it and the permissive option is disabled for them.
  2. Enterprise: where you run GitHub Enterprise Cloud, set the same default at enterprise level so new organisations start restricted. A restricted default at a higher level applies to everything beneath it.
  3. Repository (personal accounts or exceptions): Settings > Actions > General > Workflow permissions. GitHub states new repositories in a personal account start with read access for contents and packages only.
  4. Create and approve pull requests: in the same section, leave "Allow GitHub Actions to create and approve pull requests" off unless a named automation needs it. GitHub's REST reference flags enabling the related can_approve_pull_request_reviews field as a security risk, because it lets a workflow approve its own changes.
  5. By API: PUT /orgs/{org}/actions/permissions/workflow or PUT /repos/{owner}/{repo}/actions/permissions/workflow with default_workflow_permissions set to read and can_approve_pull_request_reviews set to false. A repository call returns 409 when the organisation blocks the change.

3. Lock down fork and privileged triggers

  • Fork pull requests: for pull requests from forks, GitHub downgrades write permissions to read-only unless "Send write tokens to workflows from pull requests" is selected. For private repositories, leave that setting and "Send secrets to workflows from pull requests" off, and keep "Require approval for fork pull request workflows" on.
  • pull_request_target: receives a read and write repository token even from a public fork. Give these workflows explicit minimal permissions blocks, never check out and run the pull request's code in them, and list them for review.
  • Dependabot runs: GitHub treats Dependabot-triggered runs like fork runs: read-only token and no secrets. Workflows that need to act on Dependabot pull requests should be designed for that rather than widened.
  • Do not swap in a broad personal access token: events triggered by the GITHUB_TOKEN do not start new workflow runs, apart from workflow_dispatch and repository_dispatch, and pull requests it opens wait for approval. GitHub suggests a GitHub App installation token or a personal access token if runs must chain; prefer a narrowly installed GitHub App. See the GitHub Personal Access Tokens Security Guide.

Verify it worked

  1. Defaults by API: GET /orgs/{org}/actions/permissions/workflow returns default_workflow_permissions: read and can_approve_pull_request_reviews: false. Run the repository variant across all repositories to find any set differently.
  2. Workflow files: scan every workflow for a top-level permissions key and for write-all. Scorecard's Token-Permissions check does this per repository and warns on sensitive job-level write scopes such as contents, packages and actions. Scorecard notes it cannot see the repository default setting, so pair it with the API check.
  3. Negative test: in a pilot repository, add a step that tries a write the job was not granted (for example, labelling an issue from a read-only job) and confirm it fails.
  4. Change watch: alert on changes to Actions settings in the audit log. See the GitHub Audit Log Detection Guide.
MetricTarget (our suggestion)Source
Organisations and repositories with read default100%REST API
Workflows with a top-level permissions key100% of active workflowsFile scan or Scorecard
Workflows using write-allZeroFile scan
pull_request_target workflows reviewed in the last quarterAllReview log
Repositories allowing Actions to approve pull requestsZero, or named exceptionsREST API

Rollback and common errors

The default is one setting and can be switched back at the organisation or repository level in seconds, so rollback is easy. The better rollback is narrower: when a workflow fails, add the missing scope to that job instead of reopening the default for everyone.

SymptomLikely causeFix
Release job fails to push a tag or create a releaseJob has contents: readGrant contents: write to that job only
Checkout fails after adding issues: writeUnlisted contents became noneAdd contents: read to the same block
Bot comment on pull request failsMissing pull-requests: writeGrant it to the commenting job
Cloud login step failsMissing id-token: writeGrant it to the deploying job only
Repository setting cannot be changed (409 by API)Organisation policy controls itChange at organisation level or leave as is
Fork pull request workflow cannot writeExpected fork downgradeSplit into an unprivileged check and a trusted follow-up workflow

Hardening afterwards

Where this sits in common frameworks (our mapping):

FrameworkReferenceHow this task supports it
NIST SP 800-53 Rev 5AC-6 Least PrivilegePer-job scopes and read-only defaults
NIST SP 800-53 Rev 5CM-2 Baseline ConfigurationOrganisation-wide default and workflow file standard
NIST CSF 2.0PR.AA-05Access permissions defined, enforced and reviewed with least privilege
NIST CSF 2.0PR.PS-06Secure software development practices in the pipeline

Practitioner checklist

  • Inventory workflows and record which lack a permissions key.
  • Add a top-level permissions: {} or contents: read to every workflow.
  • Grant write scopes per job, listing contents: read where a job also checks out code.
  • Set the restricted default at enterprise and organisation level.
  • Turn off "Allow GitHub Actions to create and approve pull requests" except for named automations.
  • Keep fork write tokens and fork secrets off; require approval for fork workflows.
  • Review every pull_request_target workflow and give it minimal explicit scopes.
  • Check defaults by REST API and workflow files by scan or Scorecard on a schedule.
  • Alert on Actions settings changes in the audit log.

Standards and references

Related NHI Mgmt Group resources: GitHub and GitHub Actions Security Guide · GitHub Organisation Security Checklist · CI/CD Pipeline Identity Security Guide · OpenSSF Scorecard Guide

Free weekly newsletter

Subscribe to the NHI & AI Identity Journal

The latest on NHI and Agentic AI security – articles, research, breaches, news and events every week.

Bonus 33% off our NHI Course when you subscribe.

NHIMG Editorial Note
Researched and written by the NHI Mgmt Group editorial team using AI tools, under the editorial direction of Lalit Choda. Facts are drawn from the sources listed on this page and checked by our automated quality process. Last updated 11 November 2026. Spotted an error? Use our contact page.
NHI Mgmt Group, the independent authority on Non-Human Identity, IAM and Agentic AI security. nhimg.org