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

GitHub Actions permissions: the GITHUB_TOKEN

30 min de lecture

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.

Fenêtre de terminal
# 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

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 more than 700 free guides, with no ads and no tracking. 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