The pull_request_target and workflow_run triggers execute GitHub Actions workflows using the target repository’s
credentials, including its secrets and write permissions. When the workflow then executes any part of that checked-out code — directly or indirectly
through build scripts, configuration files, or package manifests — an attacker controlling the fork gains arbitrary code execution with the host
repository’s full privileges.
The pull_request_target and workflow_run triggers run with the host repository’s credentials, including its secrets and
any write permissions granted to the GITHUB_TOKEN. When such a workflow checks out code from a fork — and that code is executed directly,
or can affect execution indirectly (for example through build scripts or downloaded package lists) — an attacker controlling the fork can run
arbitrary code with the host repository’s full privileges. Special considerations apply to organizations and repositories created before 2023, when the default permissions of
the GITHUB_TOKEN were changed to read-only.
If the workflow has access to repository secrets, malicious fork code can exfiltrate them — including deployment tokens, API keys, or cloud credentials — to an attacker-controlled server.
With write access available through the GITHUB_TOKEN, an attacker can push malicious commits, tamper with releases, or alter workflow
files to establish a persistent foothold in the repository.
If the workflow does not require write access to the repository or access to its secrets, the recommended approach is to use the
pull_request trigger instead of pull_request_target or workflow_run.
If this is not feasible, the workflow can be split into two parts:
pull_request that performs checks not requiring elevated permissions.workflow_run on completion of the first job, that performs the actions requiring elevated
permissions.When doing this, it is important to ensure that the privileged workflow does not process input from the untrusted workflow.
The following workflow executes the build.sh script from the fork using the privileges of the repository:
name: Example
on:
pull_request_target:
jobs:
main:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }} # Noncompliant
- name: Build
run: |
./build.sh
If elevated permissions are not needed, the simplest fix is to use pull_request as the trigger.
name: Example
on:
pull_request:
jobs:
main:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Build
run: |
./build.sh