Introduction
GitHub Actions workflows often need a token to read repositories, create pull requests, update issues, upload artifacts, or interact with other GitHub resources. A common mistake is granting the workflow more permissions than it actually needs.
This becomes particularly important when a workflow processes Dependabot pull requests or security-related automation.
The principle is simple: a workflow should receive only the permissions required for the job it performs. If a job only needs to read repository contents, it should not receive write access to issues, pull requests, deployments, or other unrelated resources.
GitHub Actions supports granular GITHUB_TOKEN permissions, allowing developers to reduce the scope available to workflows and individual jobs.
This article explains how to apply smaller token scopes to Dependabot-related workflows, how permissions interact with jobs, common mistakes, and practical ways to troubleshoot permission failures.
What Is the GitHub Actions GITHUB_TOKEN?
GitHub automatically provides a short-lived GITHUB_TOKEN to workflows. It allows a workflow to authenticate with GitHub APIs and perform operations against the repository.
A workflow can control the permissions granted to the token using the permissions key.
For example:
name: Dependabot Checks
on:
pull_request:
permissions:
contents: read
jobs:
security-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run checks
run: ./scripts/security-check.sh
The important part is:
permissions:
contents: read
The workflow is explicitly requesting read access to repository contents rather than broad write permissions.
Why Smaller Token Scopes Matter
A GitHub Actions workflow is executable code. If a workflow, third-party action, dependency, or script is compromised, the permissions available to the workflow can affect the potential impact.
Compare these two configurations:
permissions: write-all
and:
permissions:
contents: read
The second configuration gives the workflow a much narrower authorization boundary.
The goal is not to make every workflow read-only. The goal is to grant the minimum permissions required for its actual behavior.
This is the principle of least privilege.
Dependabot Creates an Important Security Boundary
Dependabot can create pull requests that update dependencies.
A typical workflow might run when a pull request is opened or updated:
Dependabot PR
|
v
GitHub Actions
|
+---- Build
+---- Tests
+---- Security checks
|
v
Result
The workflow may only need to inspect code and execute tests.
It may not need permission to:
- Merge pull requests
- Modify repository settings
- Create deployments
- Write issues
- Delete packages
Granting those permissions unnecessarily increases the workflow's authorization surface.
Start With Workflow-Level Permissions
A good starting point is to define permissions at the workflow level.
For example:
name: Dependency Validation
on:
pull_request:
permissions:
contents: read
jobs:
validate:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Run tests
run: dotnet test
This gives the workflow a clear default.
If the workflow does not need to write to GitHub resources, do not grant write permissions simply because an action might support them.
Use Job-Level Permissions When Jobs Have Different Needs
Different jobs in the same workflow may require different access.
For example:
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: dotnet test
report:
permissions:
contents: read
issues: write
runs-on: ubuntu-latest
steps:
- name: Publish report
run: ./scripts/publish-report.sh
The test job keeps the smaller permission set.
Only the report job receives the additional permission it needs.
This is generally preferable to granting issues: write to the entire workflow.
Common Permissions
Some frequently used GITHUB_TOKEN permissions include:
Permission | Typical purpose |
|---|---|
| Read repository contents |
| Modify repository contents |
| Read pull request information |
| Modify pull requests |
| Read issues |
| Create or modify issues |
| Read workflow-related information |
| Create or update check runs |
| Read packages |
| Publish or modify packages |
| Upload certain security analysis results |
The correct permission set depends on what the workflow actually does.
A Read-Only Dependabot Workflow
Suppose a repository uses Dependabot and wants to run .NET tests against dependency update pull requests.
A minimal workflow could be:
name: Dependabot Validation
on:
pull_request:
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.x'
- name: Restore
run: dotnet restore
- name: Build
run: dotnet build --no-restore
- name: Test
run: dotnet test --no-build
The workflow performs repository operations but does not need to modify pull requests or issues.
Therefore, contents: read may be sufficient for the GitHub token used by these actions.
Always verify the permissions actually required by the actions and commands in your specific workflow.
When Dependabot Workflows Need More Permissions
Some workflows do more than test a pull request.
For example, a workflow might add a label:
Dependabot PR
|
v
Run tests
|
v
Add "dependencies" label
That operation requires additional GitHub permissions.
Instead of giving the entire workflow broad write access, scope the permission to the job that performs the operation:
jobs:
label:
permissions:
contents: read
pull-requests: write
runs-on: ubuntu-latest
steps:
- name: Apply label
run: ./scripts/add-label.sh
The exact permission required depends on the API operation and event context.
permissions: {} as a Strong Starting Point
For workflows that do not need GitHub API access, you can start with:
permissions: {}
Then add only the permissions required by individual jobs.
For example:
permissions: {}
jobs:
build:
permissions:
contents: read
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: dotnet test
This makes the workflow's permission requirements explicit.
It can be useful for security-sensitive repositories because new permissions are added intentionally rather than inherited from a broad default.
Be Careful With Third-Party Actions
A workflow's effective security boundary is not limited to the commands you write directly.
Consider:
- uses: some-org/some-action@v1
That action executes code inside your workflow environment.
Before granting it additional token permissions, determine why the action needs them.
Prefer:
permissions:
contents: read
over:
permissions: write-all
unless the workflow genuinely requires broad write access.
Pinning third-party actions to an appropriate immutable reference can also improve supply-chain controls, depending on your repository's security policy.
Dependabot and Forked Pull Requests
Pull requests from external sources require additional care because GitHub applies security restrictions around workflow tokens and secrets.
Dependabot pull requests should not automatically be treated like trusted internal branches.
For example, avoid assuming that every pull request has access to repository secrets.
A secure workflow should be designed around the event type and trust boundary rather than assuming that:
pull_request = trusted code
This is especially important when workflows execute code from the pull request.
Dependency updates can change build scripts, package installation behavior, and other executable content.
Do Not Put Secrets Into the Token
The GITHUB_TOKEN is not a replacement for application secrets.
If a workflow requires a separate service credential, store it using the appropriate GitHub Actions secret or identity mechanism.
Keep permissions and secrets separate:
GITHUB_TOKEN
|
+-- GitHub repository authorization
External Secret
|
+-- External service authorization
Reducing GITHUB_TOKEN permissions does not eliminate the need to protect other credentials.
Troubleshooting Permission Errors
A common error looks conceptually like:
Resource not accessible by integration
or an API operation returns a permission-related failure.
Do not immediately change the workflow to write-all.
Use a structured process.
Step 1: Identify the Failing Operation
Determine exactly which command or action failed.
For example:
Checkout
Create check
Update pull request
Create issue
Upload security result
Step 2: Identify the Required Permission
Check the GitHub API operation or action documentation to determine which token permission is required.
Step 3: Add Only That Permission
For example:
permissions:
contents: read
pull-requests: write
rather than:
permissions: write-all
Step 4: Consider Event Restrictions
Some workflow events have different token and security behavior.
If the permission appears correct but the operation still fails, inspect the event and repository settings.
Step 5: Check Organization and Repository Policies
Organization-level policies can affect what workflows are allowed to do.
Repository settings can also influence workflow permissions.
Common Mistakes
Using write-all
This often solves permission errors quickly but creates a much larger authorization surface than necessary.
Giving Permissions at Workflow Level
If only one job needs write access, avoid giving every job that permission.
Assuming contents: write Covers Everything
Different GitHub resources have different permission scopes.
Ignoring Third-Party Actions
An action executes code in the workflow environment and should be treated accordingly.
Assuming Dependabot PRs Are Identical to Internal PRs
The security context of a workflow depends on how it was triggered and where the code originated.
Fixing Every Error by Adding More Permissions
Permission errors should lead to identifying the exact missing scope, not automatically expanding the token.
Best Practices
Start workflows with the smallest practical token scope.
Use
permissions: {}when GitHub API access is not required by default.Prefer
contents: readfor read-only repository workflows.Scope additional permissions to individual jobs.
Grant write access only to jobs that actually need it.
Review permissions whenever a workflow changes.
Audit third-party actions before giving them elevated permissions.
Treat Dependabot and external pull requests as distinct trust boundaries.
Keep external service credentials separate from
GITHUB_TOKEN.Investigate permission failures before increasing token scope.
Document why elevated permissions exist.
Periodically review unused permissions.
Advantages and Disadvantages
Advantages | Disadvantages |
|---|---|
Reduces the impact of compromised workflow code | Requires more deliberate workflow design |
Makes authorization easier to understand | Some actions need additional investigation |
Supports least-privilege security | Permission errors may require troubleshooting |
Limits unnecessary write access | Organization policies can add complexity |
Makes security reviews easier | Permissions may need updates as workflows evolve |
Production Checklist
Before approving a Dependabot-related workflow, verify:
[ ] Workflow permissions are explicitly defined
[ ] Unnecessary write permissions are removed
[ ] Job-level permissions are used where appropriate
[ ] Third-party actions have been reviewed
[ ] Dependabot event behavior is understood
[ ] External pull request trust boundaries are considered
[ ] Secrets are not used as a substitute for token permissions
[ ] Failed API calls have been mapped to required scopes
[ ] Elevated permissions have a documented reason
[ ] Permissions are reviewed after workflow changes
Summary
Smaller token scopes make GitHub Actions workflows easier to secure and easier to review.
For Dependabot workflows, start by determining what the job actually needs. A workflow that only checks out code and runs tests may require little more than repository read access. A job that modifies pull requests, creates issues, or uploads security results may require additional permissions.
The important practice is to scope permissions as close as possible to the operation that needs them.
Instead of using broad permissions to make a workflow work, identify the exact GitHub resource being accessed and grant the smallest corresponding permission. Job-level permissions are particularly useful when different parts of a workflow have different security requirements.
For production repositories, treat GITHUB_TOKEN permissions as part of the workflow's security boundary and review them whenever automation changes.

Join the conversation! Your thoughts help the community grow.