# GITHUB_TOKEN Permissions — GitHub Actions

Source: https://www.skillbyai.com/en/github-actions/x-perms

> Least privilege per workflow and job.

## Declare what the token may do

Each run gets an automatic `GITHUB_TOKEN` scoped to the repository. Set `permissions:` at the workflow or job level to the minimum needed (`contents: read` for CI; add `pull-requests: write` to comment, `packages: write` to publish, `id-token: write` for OIDC). Once you specify any permission, unspecified scopes become `none`. Set the organisation or repository default to read-only so new workflows start safe. Scope names are checked by actionlint, as the earlier lint showed for pull-request versus pull-requests.

## Least privilege, untrusted input, pinned dependencies

Workflows hold credentials and run code; treat them as production infrastructure.

![Four ideas: token permissions, script injection, pinning actions, secrets and OIDC.](assets/figures/github-actions/section-6-map.svg) — Figure 6.1 — Permissions, injection, pinning and OIDC.

## Minimal permissions per job

Not linted or run here; check the GitHub Actions documentation for current syntax.

```yaml
permissions:
  contents: read            # default for every job in this workflow

jobs:
  test:
    runs-on: ubuntu-latest
    steps: [ { uses: actions/checkout@v4 }, { run: npm test } ]
  comment:
    needs: test
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: write  # only this job may comment
    steps: [ { run: gh pr comment "$PR" --body "Tests passed" } ]
```

## Default the org to read-only

Organisation settings can make the token read-only by default, forcing workflows to request write access explicitly.

**Quiz:** What happens to unspecified scopes once you set permissions:?

- [ ] They keep the repository default
- [ ] They become write
- [x] They are set to none
- [ ] The workflow fails to start

*Answer:* They are set to none. Explicit allow-list.
