C.W.K.
Stream
Lesson 07 of 12 · published

GITHUB_TOKEN

~12 min · github-token, permissions, scope

Level 0Apprentice
0 XP0/101 lessons0/10 achievements
0/120 XP to next level120 XP to go0% complete

The auto-generated runner identity

Every workflow run gets an automatically issued GITHUB_TOKEN. It's an installation token for the runner, scoped to the current repo. Use it to call the GitHub API without provisioning a personal access token.

Available as ${{ secrets.GITHUB_TOKEN }} or ${{ github.token }}. Lives only for the duration of the run, then expires.

Default permissions are too generous

Until 2023, GITHUB_TOKEN defaulted to write on most things (contents, issues, pull-requests). Now the org default can be either 'permissive' (legacy) or 'restricted' (read all). Always check and prefer restricted.

Scope per workflow with permissions::

  • contents: read — checkout, no push.
  • contents: write — push commits, tags, releases.
  • pull-requests: write — comment on PRs, create PRs.
  • issues: write — comment on issues.
  • id-token: write — request OIDC tokens (covered in next lesson).
  • packages: write — push to GHCR.
  • security-events: write — upload SARIF for code scanning.

Code

Least-privilege workflow·yaml
name: ci
on: { pull_request: {}, push: { branches: [main] } }

# Workflow-wide default: read-only.
permissions:
  contents: read

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

  comment-on-pr:
    if: github.event_name == 'pull_request'
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write   # only this job needs write
    steps:
      - uses: actions/github-script@v7
        with:
          script: |
            github.rest.issues.createComment({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.issue.number,
              body: 'CI ran successfully ✓',
            });

External links

Exercise

Audit each workflow in your repo. Add an explicit top-level permissions: block defaulting to contents: read. Override per-job for the few that need more. Push and watch for any new failures (those are real findings).

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.