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

contents: read

Read repository contents

contents: write

Modify repository contents

pull-requests: read

Read pull request information

pull-requests: write

Modify pull requests

issues: read

Read issues

issues: write

Create or modify issues

actions: read

Read workflow-related information

checks: write

Create or update check runs

packages: read

Read packages

packages: write

Publish or modify packages

security-events: write

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

  1. Start workflows with the smallest practical token scope.

  2. Use permissions: {} when GitHub API access is not required by default.

  3. Prefer contents: read for read-only repository workflows.

  4. Scope additional permissions to individual jobs.

  5. Grant write access only to jobs that actually need it.

  6. Review permissions whenever a workflow changes.

  7. Audit third-party actions before giving them elevated permissions.

  8. Treat Dependabot and external pull requests as distinct trust boundaries.

  9. Keep external service credentials separate from GITHUB_TOKEN.

  10. Investigate permission failures before increasing token scope.

  11. Document why elevated permissions exist.

  12. 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.