Give each automation job only the permissions it writes with. Set the permissions key explicitly, start from read access to contents, and add one write scope per job, such as issues write for ticket creation. Anything broader turns a small script bug into a repository incident.
Why this matters for automation
Security automation runs unattended, handles untrusted text such as pull request titles and advisory descriptions, and often has write access. That combination is where mistakes become incidents. GitHub's documentation says that actions can reach the token through the github.token context even when you do not pass it in, and recommends that you always limit the token to the minimum access needed.
The permissions key can be set for a whole workflow or for individual jobs. GitHub's tutorial examples set it at job level: one job has contents: read and issues: write, another only issues: write. If you need access the GITHUB_TOKEN cannot offer, the docs point to a GitHub App installation token, or alternatively a personal access token stored as a secret.
A permission map
| Automation job | Typical permissions |
|---|---|
| Create tickets as GitHub issues | issues: write |
| Label pull requests by path | pull-requests: write, contents: read |
| Comment scan results on a pull request | pull-requests: write |
| Upload code scanning results | security-events: write |
| Scheduled report to chat | contents: read only |
| Read advisories and open a fix branch | contents: write, pull-requests: write |
Check the exact scope names in GitHub's workflow syntax reference for your case, because they change as features are added. Our rule of thumb: if a job posts to an outside service such as chat or Jira, it often needs only contents: read on the GitHub side, since its write access lives in a separate secret.
Steps to tighten an existing workflow
- List every job and what it changes.
- Add a top-level
permissions: {}orcontents: readto set a low default. - Add job level permissions only where a write is needed.
- Run the workflow and read the failures. A 403 tells you which scope is missing.
- Re-run, and record the final map in the repository's docs.
Secrets travel with permissions
Every secret a job can read is an extra permission. GitHub documents environment secrets that are only released to jobs that pass the environment's rules. Keep chat webhook URLs and ticketing tokens in a narrower scope than your deploy keys, and do not expose them to jobs that run on pull requests from forks. For related reading see one connection per automation and personal login or service account.
Review the map regularly
Permissions creep. Someone adds write-all to get a build green, and it stays. Put workflow files under code owners review and grep for broad grants as part of a quarterly check. Our guardrails checklist for security automation includes the full list.
Quick check
Is the default token read only?
That depends on your repository and organization settings, so do not assume. Set the permissions key explicitly in each workflow.
When do I need a GitHub App instead?
When the job needs access the GITHUB_TOKEN cannot provide, or needs to trigger further workflow runs.
Sources
- GitHub Docs: use GITHUB_TOKEN for authentication in workflows
- GitHub Docs: managing environments for deployment
Safeguard offers agent identity and policy features for governing what automated agents are allowed to do. Try the free scan at gold.safeguard.sh/scan.
Last reviewed: 19 October 2026.