Safeguard
Security Guides•

How should you set GITHUB_TOKEN permissions for fork pull requests?

Set the default GITHUB_TOKEN to read-only for repository contents, raise it per job, and know that fork pull requests get a read-only token regardless.

Safeguard Security Team
4 min read

Set the default GITHUB_TOKEN permission to read-only for repository contents, then raise it job by job where a job needs more. Pull requests from forks already receive a read-only token on GitHub, but your other triggers do not, so the default and the per-job declarations still matter.

What the token is

GITHUB_TOKEN is a short-lived credential GitHub creates for each workflow run. Any action in the job can read it through the github.token context, even if you never pass it in. That is why GitHub advises limiting the token to the minimum access an action needs.

What fork pull requests get

For the pull_request event from a fork, the token is read-only and secrets are not passed to the runner. That protects you from a hostile change in the pull request. It does not protect you from your own workflows that use a different trigger, or from a repository setting that grants broad token permissions to everything else.

Steps to set a safe default

  1. In repository or organization settings for Actions, set the default workflow permissions to read repository contents, nothing else.
  2. In each workflow file, add a top-level permissions block that grants nothing or read-only access.
  3. Raise permissions on a single job that needs it. For example, a job that comments on pull requests needs pull request write access, and nothing else.
  4. Keep jobs that process untrusted input separate from jobs that hold write access.
  5. Review the permissions block in code review whenever a workflow file changes.

Here is a minimal pattern.

permissions:
  contents: read

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

A separate reporting job can declare its own broader permissions, so the test job never holds them.

Why per job beats per workflow

If a workflow has one broad permissions block, every step in every job inherits it, including third party actions. A compromised action in a test job then runs with write access. Narrow permissions per job mean that a compromise in one place cannot do work reserved for another.

GitHub also notes that any user with write access to a repository has read access to all secrets configured in it, so the credentials used in workflows should have the least privilege required.

Common mistakes

  • Granting write permissions at the top of a workflow because one job needed them.
  • Assuming the fork read-only token means all triggers are safe.
  • Forgetting that scheduled and push workflows run with the repository default, which may be broader than you expect.
  • Passing a personal access token instead of the scoped token, which bypasses the whole idea.

Try it yourself

Click through it at your own pace, or open it full screen.

Notice that the gate needs to read the change to make a decision, which is the kind of job that should hold read-only access.

Where Safeguard fits

Safeguard policies and gates read the pull request and report a decision, so the checking step does not need write access to your repository to do its job. A free scan shows what it would report on your code.

Quick check

Is the token read-only for every pull request?

No. GitHub documents it as read-only for pull requests from forks. Pull requests from branches in the same repository follow your repository default.

Can an action read the token if I do not pass it?

Yes. An action can read it through the github.token context, so limit the permissions.

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.