Safeguard
Security Guides•

What permissions should the GitHub Actions token have?

Set the GITHUB_TOKEN default to read-only for contents, then grant write access per job. Here is how to do it and what breaks when you tighten it.

Safeguard Security Team
4 min read

The GITHUB_TOKEN should start as read-only for repository contents, and each job should then ask for the extra scopes it truly needs, such as pull requests write or packages write. GitHub's own guidance recommends this default, because any step in a job, including third-party actions, can read the token.

What the token is

Every workflow run gets a short-lived token called GITHUB_TOKEN. It lets the run call the GitHub API as the workflow, for things like commenting on a pull request or pushing a package. It expires when the job ends.

The part that surprises people is who can see it. GitHub's documentation states that an action can reach the token through the github.token context even when your workflow never passes it in. So a compromised or careless action in any step has whatever access the token has. That is the whole reason to trim it.

Step 1: set a safe default

In repository settings, under Actions and then General, there is a "Workflow permissions" section. It offers a permissive choice (read and write for everything) and a restricted choice (read for contents and packages only). Pick the restricted one.

GitHub's docs say new repositories in a personal account already start restricted. New repositories in an organization inherit whatever the organization setting says, and a stricter organization setting disables the permissive option for its repositories. So for a small team the quickest win is to set it once at the organization level.

Step 2: declare permissions in the workflow

Add a permissions block at the top of each workflow file. Give the whole file the minimum, then widen a single job.

permissions:
  contents: read

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm test

  comment:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: write
    steps:
      - run: echo "post the test summary here"

The job level block replaces the workflow level one for that job, so list every scope the job needs, including contents.

Step 3: handle what breaks

Tightening the token will break a few workflows the first time. The log usually tells you which scope was refused. Typical cases:

SymptomScope to add, on that job only
Cannot comment on a pull requestpull-requests: write
Cannot open or label an issueissues: write
Cannot push a package to the registrypackages: write
Cannot push a commit or tagcontents: write

Resist the urge to flip the repository default back to permissive. Fix the one job.

When the token is not enough

If a job needs access the token cannot have, GitHub's docs point to two options: a GitHub App that mints an installation token inside the workflow, or a personal access token stored as a secret. Prefer the App, because a personal token is tied to a person and usually broader than the job needs. See our post on which CI credential a small team should use for the trade offs.

A related setting is worth checking in the same visit. GitHub lets you block workflows from creating or approving pull requests. Left on, automation can approve its own changes. The docs flag that as a risk if the pull request merges without real oversight.

How Safeguard fits

Safeguard's policies and gates can fail a pull request that adds a vulnerable dependency, and the free scan at gold.safeguard.sh/scan gives a first look at a repository. Token scoping itself is a setting you control in GitHub.

Quick check

Does a read-only token stop a malicious action from stealing it?

No. It limits what a stolen token can do, but the action can still read secrets you pass to that job. Pin actions and keep secrets out of jobs that do not need them.

Do I need permissions in every workflow if the default is restricted?

It is still good practice. An explicit block documents intent and protects you if someone later loosens the repository default.

Sources

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.