Why is this an issue?

Granting read permissions at the workflow level applies those permissions to all jobs in the workflow by default. This violates the principle of least privilege, as jobs that don’t require these read permissions will still inherit them unnecessarily.

What is the potential impact?

When read permissions are granted at the workflow level, all jobs inherit these permissions regardless of whether they need them. This creates several security risks:

Unauthorized reconnaissance

Jobs with unnecessary pull-requests: read or issues: read permissions could be exploited to gather sensitive information from pull request discussions, issue comments, or code review feedback. This information could reveal security vulnerabilities, internal processes, or other sensitive details that could be used for targeted attacks.

Security event exposure

Jobs that inherit security-events: read permissions could access security vulnerability data, dependency information, or other security-related metadata. While this information may seem less critical than write access, it could still be used by attackers to identify vulnerable components or plan targeted attacks based on known security issues.

How to fix it

Move sensitive permissions from the workflow level to individual job levels. Only grant permissions at the job level to jobs that actually require them. This ensures that each job receives only the minimum permissions necessary for its function.

Code examples

Noncompliant code example

The following workflow grants contents: read and issues: read permissions at the workflow level, which applies to all jobs:

name: Example

on:
  workflow_dispatch:

permissions:
  contents: read # Noncompliant
  issues: read  # Noncompliant
jobs:
  read-contents:
    runs-on: ubuntu-latest
    steps:
      - name: Read repository contents
        run: |
          # Uses contents: read permission

  read-issues:
    runs-on: ubuntu-latest
    steps:
      - name: Read issues
        run: |
          # Uses issues: read permission

Compliant solution

Grant permissions only at the job level to jobs that require them:

name: Example

on:
  workflow_dispatch:

jobs:
  read-contents:
    permissions:
      contents: read
    runs-on: ubuntu-latest
    steps:
      - name: Read repository contents
        run: |
          # Uses contents: read permission

  read-issues:
    permissions:
      issues: read
    runs-on: ubuntu-latest
    steps:
      - name: Read issues
        run: |
          # Uses issues: read permission

How does this work?

The key difference lies in how GitHub Actions applies permissions at different levels:

By placing permissions at the job level, you ensure that each job receives only the permissions it explicitly declares, preventing unnecessary permission inheritance and reducing the attack surface of your workflows.

Resources

Documentation

Standards

Related rules