Anchorflow

GITHUB_TOKEN permissions: how to set least privilege

Every job gets a GITHUB_TOKEN. Its scopes come from the permissions block, and if you never write one, from the repository's default setting, which on older repositories and many organisations is read and write for everything.

How the grant is decided

A top-level permissions block applies to every job. A job's own block replaces it for that job: scopes you do not list in the job block are set to none. Writing permissions: {} at the top removes every scope, so each job has to ask for what it needs.

A least-privilege pattern

Start from nothing and grant per job. Only the job that publishes gets write access, and only to the scope it writes.

permissions: {}

jobs:
  test:
    runs-on: ubuntu-latest
    permissions:
      contents: read
    steps:
      - uses: actions/checkout@<sha>  # v4

  release:
    runs-on: ubuntu-latest
    permissions:
      contents: write   # this job pushes the release tag
    steps:
      - uses: actions/checkout@<sha>  # v4

Common over-grants

permissions: write-all at the top or on a job. No permissions block at all, so the repository default applies. A write scope on a job whose steps only read. id-token: write on a job with no step that requests an OIDC token. Each one widens what injected code could do with the token.

What Anchorflow checks

Anchorflow builds a grant table for every job, showing which scopes are set by the top-level block and which by the job's own block, and flags write-all, unset permissions, write scopes that no step needs and unused id-token: write. Each finding names the job and the line and says what to change.

Check your workflow