Add a permissions block with contents set to read at the top of the file, then grant extra scopes to individual jobs that truly need them. Without a block, the workflow depends on whatever default your repository or organization chose, which you may not have checked.
What the token is
Every workflow run receives a generated token, available as secrets.GITHUB_TOKEN and through the github.token context. GitHub says an action can reach the token through that context even if the workflow never passes it in. So every action in the file, including ones you did not write, can use it.
GitHub also says the token's permissions are limited to the repository that contains the workflow and that it expires when the job finishes. Those limits help. They do not change the advice to give it the least access it needs.
The recommended shape
GitHub's secure use guide says it is good practice to set the default for the token to read access for repository contents only, and then increase permissions for individual jobs. In YAML, that looks like this:
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- run: echo "build and test here"
comment:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- run: echo "this job may write pull request comments"
The top block sets the floor. The second job opts in to one extra scope. If a job needs nothing from the token, you can go further and set an empty block for it.
Why it matters for copied files
If someone abuses a compromised step, the damage depends on what the token can do. GitHub's page on compromised runners says an attacker with the job's token can modify repository contents, including releases, if the assigned permissions are not restricted. A starter workflow that only runs tests has no reason to carry that power.
A short decision table
| The job does this | Permission to consider |
|---|---|
| Checks out code and runs tests | contents read |
| Posts a pull request comment | pull-requests write, in that job only |
| Opens or labels issues | issues write, in that job only |
| Publishes a release | contents write, in a release job with review |
| Needs another repository | A GitHub App token or similar, not wider token scopes |
GitHub's guidance for needing more than the token can give is to create a GitHub App and generate an installation token in the workflow, or to store a personal access token as a secret. A narrowly scoped app is usually the cleaner choice.
Check the defaults too
Open the Actions settings for your repository and organization and see what the default token permission is. Set it to read, so a workflow with no block is safe by default. Then your explicit blocks become documentation of intent, not the only line of defense. See GITHUB_TOKEN least privilege for more patterns.
Quick check
Does a read only default make the block unnecessary?
The block still helps. It records intent in the file and keeps the workflow safe if someone later changes the repository default.
Can a job grant itself more than the workflow level block?
A job level block sets that job's own permissions. That is why changes to workflow files need review and code owners, since a write access user can edit them.
Sources
- Use GITHUB_TOKEN for authentication in workflows, GitHub Docs
- Secure use reference, GitHub Docs
- Compromised runners, GitHub Docs
Last reviewed: 19 October 2026.