Skip to content
Français
Français
CI/CD & Automatisation medium

GitHub Actions permissions: the GITHUB_TOKEN

30 min read

Read this page in French

The GITHUB_TOKEN is generated automatically for every workflow run. It lets you talk to the GitHub API: clone the repository, open issues, publish packages. Badly configured, it becomes a major attack vector.

What you will learn

  • Apply least privilege to the GITHUB_TOKEN
  • Declare permissions explicitly in every workflow
  • Choose between workflow-level and job-level permissions
  • Know each available permission and the risk attached to it
  • Check compliance with Scorecard's Token-Permissions check

The principle of least privilege

The principle of least privilege means granting only the permissions that are strictly necessary. If a workflow is compromised, the damage stays limited to what that workflow was allowed to do.

Default permissions

The default permissions of the GITHUB_TOKEN depend on the age of the repository and on its configuration. It is a classic trap: an old repository still grants broad write rights by default.

Repository typeDefault permissions
Created after February 2023contents: read only
Created before February 2023Read AND write on everything

Check your settings

Under Settings > Actions > General > Workflow permissions, select "Read repository contents and packages permissions" to force minimal default permissions.

Declaring permissions explicitly

Even with restrictive repository settings, always declare the permissions in your workflows: the repository configuration can change, whereas the workflow stays explicit and auditable.

name: Build and Test
permissions:
contents: read # Read the code only
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-24.04
steps:
- name: Check out the code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Install and test
run: npm ci && npm test

Workflow-level versus job-level permissions

GitHub Actions lets you declare permissions at two levels. The choice between them decides the blast radius if a single job is compromised.

Workflow-level

Permissions declared at the workflow level apply to every job, including those that do not need them.

# ⚠️ Every job holds packages:write
permissions:
contents: read
packages: write
jobs:
test:
runs-on: ubuntu-24.04
steps:
- run: npm test # Does not need packages:write
publish:
runs-on: ubuntu-24.04
steps:
- run: npm publish

Keep the workflow level to the strict minimum and declare write permissions on each job that genuinely needs them.

permissions:
contents: read # Minimal for the workflow
jobs:
test:
runs-on: ubuntu-24.04
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- run: npm test
publish:
needs: test
runs-on: ubuntu-24.04
permissions:
contents: read
packages: write # For this job only
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- run: npm publish

The benefit: if the test job is compromised, the attacker cannot publish a package.

Available permissions

Here are the permissions you will grant most often, ranked by risk. The rule stays the same: enable write only on the job that strictly needs it.

PermissionUseRisk if abused
contents: readClone the repositoryLow
contents: writePush, create releasesCode injection
packages: writePublish to GitHub PackagesMalware distribution
actions: writeModify the workflowsPersistence
id-token: writeOIDC for cloud providersInfrastructure access
pull-requests: writeComment on pull requestsSpam, phishing
issues: writeCreate or edit issuesSpam
security-events: writeUpload SARIFHiding alerts

Never use this

permissions: write-all

A workflow holding every permission can modify its own code, create malicious releases and persist inside the repository.

Scorecard Token-Permissions

OpenSSF Scorecard checks that your workflows respect least privilege. The Token-Permissions check requires write permissions to sit at the job level.

Terminal window
# Check your workflows
scorecard --local . --checks Token-Permissions --show-details

Score 0/10: write permissions at the workflow level.

# ❌ Scorecard: 0/10
permissions:
contents: read
packages: write # At the workflow level

Score 10/10: write permissions at the job level only.

# ✅ Scorecard: 10/10
permissions:
contents: read
jobs:
publish:
permissions:
packages: write # At the job level

A practical case: a complete CI/CD pipeline

This full pipeline, test, build, publish, applies least privilege job by job. It is a directly reusable model: each job receives only what it needs.

name: CI/CD Pipeline
permissions:
contents: read
on:
push:
branches: [main]
pull_request:
jobs:
test:
runs-on: ubuntu-24.04
permissions:
contents: read
steps:
- name: Check out the code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Install and test
run: npm ci && npm test
build:
needs: test
runs-on: ubuntu-24.04
permissions:
contents: read
steps:
- name: Check out the code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Build the project
run: npm run build
- name: Upload the artefact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: dist
path: dist/
publish:
if: github.ref == 'refs/heads/main'
needs: build
runs-on: ubuntu-24.04
permissions:
contents: read
packages: write
id-token: write # For OIDC
steps:
- name: Check out the code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Download the artefact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: dist
path: dist/
- name: Publish the package
run: npm publish

Check your understanding

Make sure the essentials of this guide are yours. The questions only cover what was explained here.

Knowledge check

Check what you have learned with this interactive quiz

6 questions
6 min
70% to pass

How it works

  • The timer starts when you click Start
  • Multiple choice, true/false and short answer questions
  • You can move between questions
  • Detailed results are shown at the end

Starts the quiz and the timer

Key points

  • The GITHUB_TOKEN is created for every run; an old repository grants it broad write rights by default, which is worth fixing.
  • Declare permissions: explicitly in every workflow, even when the repository settings are already restrictive.
  • Keep the workflow level minimal (contents: read) and grant write only to the job that genuinely needs it.
  • permissions: write-all must be banned: a compromised workflow could modify its own code and persist.
  • Scorecard's Token-Permissions check requires write permissions at the job level; that is the compliance signal.

For the exhaustive list of permissions, see the official documentation on the GITHUB_TOKEN.

Next steps

Is this site useful to you?

Fewer than 1% of readers support this site.

I maintain this site for free, with no ads, no ad profiling and no account to create. Any support, even a symbolic one, helps cover hosting and keeps these resources free. Thank you for the help.

The form does not show? Open Ko-fi in a new tab.

Subscribe and follow my DevSecOps work on LinkedIn