← Actions Permissions Check

How to set least-privilege permissions for GitHub Actions

Set permissions: contents: read at the workflow level, then add the one extra scope a specific job needs directly on that job — for example pull-requests: write only on the job that comments on a PR — rather than granting every job the broader set.

Most workflows never mention permissions: at all:

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

That job runs with whatever your repository's or organization's default GITHUB_TOKEN permissions are. Repositories created before 2023-02-02 default to read-write on every scope unless someone has since narrowed it in Settings → Actions → General. A workflow triggered by a pull request from a fork then runs with a token that can push commits or edit releases, even though this job only reads the checkout and runs tests.

1. Set a minimal default at the workflow level

Add a permissions: block near the top of the file, starting from contents: read — the narrowest grant most jobs actually need:

permissions:
  contents: read

Every job in the file now runs with exactly that, unless a specific job overrides it.

2. Escalate only on the job that needs more

If one job in the workflow needs to comment on a pull request, give that job the extra scope rather than widening the workflow-level default for every job:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - run: npm test
  comment:
    permissions:
      contents: read
      pull-requests: write
    runs-on: ubuntu-latest
    steps:
      - run: gh pr comment ...

The build job still runs with the workflow-level contents: read; only comment gets the extra scope, and only for the duration of that job.

3. Avoid write-all

permissions: write-all grants every scope write access at once. It is explicit, which is better than nothing, but it is rarely what a job actually needs — replace it with the specific scopes the job's steps actually use.

4. Check every workflow at once

Paste your workflow YAML into Actions Permissions Check to find every workflow and job that still relies on the ambient default, or that grants write-all, across every file at once — or run the CLI in your own repository:

python3 check.py --fail-on-risk

That exits with a nonzero status if any workflow or job has no explicit permissions grant, or grants write-all — wire it into your own CI to block a newly-added unscoped workflow before it merges.

What this does not tell you

This tool cannot see your repository's or organization's actual default token setting — there is no public API for it — so an "unspecified" finding means the file doesn't say, not that your account is definitely misconfigured. And it never judges whether the scopes a workflow does declare are the minimum a job needs; deciding that is still yours to do.