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 type | Default permissions |
|---|---|
| Created after February 2023 | contents: read only |
| Created before February 2023 | Read 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 testWorkflow-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:writepermissions: 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 publishJob-level (recommended)
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 publishThe 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.
| Permission | Use | Risk if abused |
|---|---|---|
contents: read | Clone the repository | Low |
contents: write | Push, create releases | Code injection |
packages: write | Publish to GitHub Packages | Malware distribution |
actions: write | Modify the workflows | Persistence |
id-token: write | OIDC for cloud providers | Infrastructure access |
pull-requests: write | Comment on pull requests | Spam, phishing |
issues: write | Create or edit issues | Spam |
security-events: write | Upload SARIF | Hiding alerts |
Never use this
permissions: write-allA 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.
# Check your workflowsscorecard --local . --checks Token-Permissions --show-detailsScore 0/10: write permissions at the workflow level.
# ❌ Scorecard: 0/10permissions: contents: read packages: write # At the workflow levelScore 10/10: write permissions at the job level only.
# ✅ Scorecard: 10/10permissions: contents: read
jobs: publish: permissions: packages: write # At the job levelA 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 publishCheck 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
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
Review
(0/0)Skills profile
What to do next
Resources to go further
Hints before you try again?
A new full quiz with random questions
Work again only on the questions you missed
Key points
- The
GITHUB_TOKENis 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 grantwriteonly to the job that genuinely needs it. permissions: write-allmust be banned: a compromised workflow could modify its own code and persist.- Scorecard's Token-Permissions check requires
writepermissions 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
- OIDC: authenticating without a secret: the
id-token: writepermission in action, replacing static cloud credentials with a token that lives a few minutes. - Cloud OIDC (AWS, Azure, GCP): the provider-side configuration that grants those rights to the right repository and the right branch.
- Security checklist: the recap that puts least privilege back among the other controls to verify before publishing.