GitHub Actions GITHUB_TOKEN Permissions
Every workflow run receives a temporary access key called GITHUB_TOKEN. The key lets the workflow talk to your repository: read code, post comments, create releases, and more. Controlling what the key can do protects your project from mistakes and attacks.
The Token in Simple Words
Think of a hotel key card. The front desk creates it when you check in, programs it for specific doors, and deactivates it at checkout. GITHUB_TOKEN behaves the same way. GitHub creates it when a job starts and destroys it when the job ends. No one needs to store or rotate it.
How a Workflow Uses the Token
Two expressions give access to the token.
${{ secrets.GITHUB_TOKEN }}
${{ github.token }}Many marketplace actions read the token automatically. Others ask for it as an input.
- name: Comment on issue
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh issue comment 1 --body "Build passed"The Permissions Key
The permissions key sets what the token can do. Each scope accepts three values: read, write, or none.
permissions:
contents: read
issues: write
pull-requests: writeA short rule matters here. After you list any permission, every scope you leave out drops to none. The list acts as a complete whitelist.
Common Permission Scopes
| Scope | Controls |
|---|---|
| contents | Reading code, pushing commits, creating releases |
| issues | Creating and editing issues and comments |
| pull-requests | Commenting on and labeling pull requests |
| packages | Publishing to GitHub Packages |
| actions | Managing workflow runs and artifacts |
| pages | Deploying to GitHub Pages |
| id-token | Requesting OIDC tokens for cloud login |
| security-events | Uploading code scanning results |
Workflow Level and Job Level
Place permissions at the top of the file to apply it to every job. Place it inside a job to give that job its own rules. Job-level settings replace the workflow-level settings for that job.
permissions:
contents: read # default for all jobs
jobs:
test:
runs-on: ubuntu-latest
steps:
- run: echo "read only"
release:
runs-on: ubuntu-latest
permissions:
contents: write # only this job can write
steps:
- run: echo "can publish"Least Privilege Diagram
Broad token Narrow token
+-------------------+ +-------------------+
| contents: write | | contents: read |
| issues: write | ---> | (everything else |
| packages: write | | set to none) |
| actions: write | +-------------------+
+-------------------+
Attack surface: large Attack surface: smallA compromised step in a broad-token job can rewrite your repository. The same step in a narrow-token job can only read it.
Default Permissions Setting
Repository and organization settings choose the default token power. Open Settings, then Actions, then General, and find Workflow permissions. Select read-only access as the default. Add write access in each workflow that truly needs it.
Special Cases
Pull Requests From Forks
Workflows triggered by pull requests from forks receive a read-only token. GitHub applies this rule to stop outsiders from changing your repository.
No Chain Reactions
Events created with GITHUB_TOKEN do not start new workflow runs, with the exception of manual and repository dispatch events. The rule prevents endless loops where a workflow triggers itself.
Practical Checklist
- Declare
permissionsin every workflow. - Start with
contents: read. - Add one scope at a time when a step fails with a permission error.
- Grant write scopes at job level, never at workflow level, when only one job needs them.
The error message "Resource not accessible by integration" signals a missing scope. Add the named scope and run the workflow again.
