Set permissions: contents: read at the workflow level, then add the one extra scope a specific job needs directly on that job — for example pull-requests: write only on the job that comments on a PR — rather than granting every job the broader set.
Most workflows never mention permissions: at all:
name: ci
on: pull_request_target
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm test
That job runs with whatever your repository's or organization's default GITHUB_TOKEN permissions are. Repositories created before 2023-02-02 default to read-write on every scope unless someone has since narrowed it in Settings → Actions → General. A workflow triggered by a pull request from a fork then runs with a token that can push commits or edit releases, even though this job only reads the checkout and runs tests.
Add a permissions: block near the top of the file, starting from contents: read — the narrowest grant most jobs actually need:
permissions:
contents: read
Every job in the file now runs with exactly that, unless a specific job overrides it.
If one job in the workflow needs to comment on a pull request, give that job the extra scope rather than widening the workflow-level default for every job:
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: npm test
comment:
permissions:
contents: read
pull-requests: write
runs-on: ubuntu-latest
steps:
- run: gh pr comment ...
The build job still runs with the workflow-level contents: read; only comment gets the extra scope, and only for the duration of that job.
write-allpermissions: write-all grants every scope write access at once. It is explicit, which is better than nothing, but it is rarely what a job actually needs — replace it with the specific scopes the job's steps actually use.
Paste your workflow YAML into Actions Permissions Check to find every workflow and job that still relies on the ambient default, or that grants write-all, across every file at once — or run the CLI in your own repository:
python3 check.py --fail-on-risk
That exits with a nonzero status if any workflow or job has no explicit permissions grant, or grants write-all — wire it into your own CI to block a newly-added unscoped workflow before it merges.
This tool cannot see your repository's or organization's actual default token setting — there is no public API for it — so an "unspecified" finding means the file doesn't say, not that your account is definitely misconfigured. And it never judges whether the scopes a workflow does declare are the minimum a job needs; deciding that is still yours to do.