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