How to Scope GitHub Actions Permissions Without Breaking CI
Most workflows do not need broad write access. This guide shows how to trim GitHub Actions permissions step by step.

One of the most useful GitHub Actions security improvements is also one of the easiest to delay: reducing token permissions.
Teams avoid it because they assume something will break. Sometimes it does. The fix is not to keep broad write access everywhere. The fix is to scope access with intent.
Start with the default question
For every workflow, ask:
What does this job actually need to do?
Common answers:
- read repository contents
- comment on a pull request
- update a commit status
- publish a package
- create a release
- deploy to an environment
Those are different access patterns. They should not all share the same token scope.
Use top-level permissions as a floor, not a shortcut
A good baseline is:
permissions:
contents: read
Then grant stronger permissions only in the job that needs them:
jobs:
release:
permissions:
contents: write
packages: write
This prevents a test job, lint job, or analysis job from inheriting write access by default.
Watch for hidden write paths
Some workflows need write access indirectly because they:
- create pull requests
- push version bumps
- upload packages
- write deployment statuses
- comment on issues or PRs
The answer is not to allow write-all. The answer is to identify the exact scope and confine it to the relevant job.
Common permission mistakes
write-all as a convenience setting
This is fast during setup and expensive later. It makes review harder because nobody can tell which step actually needs privilege.
Using one workflow for both untrusted and privileged paths
If a workflow runs on pull requests from forks and also needs write access, split the concerns. Keep the untrusted path read-only and move privileged operations elsewhere.
Forgetting repository-level defaults
Even if a workflow looks minimal, repository defaults may still be broader than expected. Review both the workflow file and the repository or organization settings.
A rollout plan that avoids breakage
If your team has many workflows, do not tighten everything at once. Use a staged rollout:
- Set
contents: readin low-risk workflows. - Move required write permissions to specific jobs.
- Test release and automation paths.
- Block new workflows that introduce broad permissions without justification.
This gives you fast wins without creating a migration freeze.
When write permissions are justified
Write scopes are not bad by themselves. They are bad when they are vague, inherited broadly, or mixed into untrusted execution paths.
Legitimate examples include:
- release publishing
- changelog automation
- labeling and triage bots
- deployment status updates
The goal is explicit access, not zero access.
The standard to aim for
A healthy GitHub Actions permissions model is simple:
- most workflows are read-only
- privileged jobs are obvious in review
- high-risk triggers are isolated
- engineers can explain why each write scope exists
That is enough to reduce a large class of GitHub workflow security issues before you ever reach enterprise-scale tooling.
Apply this checklist to your repositories
Use action pin to find unpinned actions, broad token permissions, risky pull request triggers, and workflow security issues before they merge.
Related Guides
Keep hardening GitHub Actions
Learn the GitHub Actions security best practices that reduce supply-chain risk without turning your small team into a DevSecOps department.
A practical rollout plan for SHA pinning in GitHub Actions, including what to pin first and how to handle updates safely.
Understand the real risk behind pull_request_target and how to separate untrusted pull request code from privileged automation.