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.

Why is this an issue?

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.

What is the potential impact?

Secret extraction

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.

Repository manipulation

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.

How to fix it

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:

When doing this, it is important to ensure that the privileged workflow does not process input from the untrusted workflow.

Code examples

Noncompliant code example

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

Compliant solution

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

Resources

Documentation

Articles & blog posts

Standards