Set the default GITHUB_TOKEN permission for your repositories to read-only on contents, then add the specific permissions each job needs with the permissions key. A compromised step can only do what the token allows, so a narrow token turns a serious incident into a small one.
Why the token matters
Every workflow run gets a GITHUB_TOKEN. Steps in the job, including third party actions, can use it. If an action is tampered with, or an injected command runs, the token is the first credential in reach.
GitHub's hardening guide states that anyone with write access to a repository can read all its secrets, and advises that credentials carry only the privileges they need. It recommends setting the default token permission to read-only for repository contents and granting more per job.
How to do it
There are two layers.
First, the repository or organization setting that controls the default. Set it to the restrictive option so that a workflow without any permissions block starts read-only.
Second, the permissions key in the workflow file. GitHub documents that you can set it for the whole workflow or for individual jobs. Job level is the better habit, because a build job and a release job rarely need the same access.
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: make test
comment:
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- run: echo "post a summary"
The top level block keeps the default tight. The comment job opts into one extra permission and nothing else.
Check what you have today
Older workflows may have been written when broader defaults applied, and they may rely on them silently. Switching the default to read-only can break a job that quietly pushes a tag or labels an issue.
A safe rollout looks like this:
- List workflows that have no
permissionsblock. - Switch the default to read-only in a test repository first.
- Fix failures by adding the one permission the job needs, at job level.
- Roll the setting out to the organization.
I could not confirm from the pages I fetched which default new repositories get today, so check your own settings page instead of assuming.
Fork pull requests
GitHub documents that in pull requests from forked repositories the GITHUB_TOKEN has read-only permissions and secrets are not passed to the runner. That is a platform guard, but it does not cover every trigger. See pull_request versus pull_request_target.
Add a review step
Narrow tokens are only useful if the permissions block cannot be widened unnoticed. Put the workflows directory under CODEOWNERS so edits need approval.
Safeguard policies can enforce checks on pull requests that touch pipeline files. You can try the free scan at gold.safeguard.sh/scan.
Quick check
Does a job level permissions block replace the top level one?
It sets the permissions for that job. Declare every permission the job needs there instead of relying on the top level block.
Is read-only enough for secret safety?
No. The token is one credential. Repository and environment secrets still need their own limits.
Sources
Last reviewed: 19 October 2026.