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: write

A 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

ScopeControls
contentsReading code, pushing commits, creating releases
issuesCreating and editing issues and comments
pull-requestsCommenting on and labeling pull requests
packagesPublishing to GitHub Packages
actionsManaging workflow runs and artifacts
pagesDeploying to GitHub Pages
id-tokenRequesting OIDC tokens for cloud login
security-eventsUploading 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: small

A 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 permissions in 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.