PR/MR Pipeline Security
This document introduces the security differences between the pull_request and pull_request_target trigger events, helping to choose the correct fork PR pipeline configuration.
PR pipelines need to execute code from external contributors (fork repositories), which poses a security risk. It is necessary to understand the security differences between pull_request and pull_request_target events and choose the appropriate configuration.
Configuration Description
Security Differences Between pull_request and pull_request_target
| Dimension | pull_request | pull_request_target |
|---|---|---|
| Code Source | Code from the fork repository (PR submission) | Code from the target repository (workflow version on the main branch) |
| ATOMGIT_TOKEN Permissions | Read-only permissions (security restrictions) | Full permissions as declared in permissions |
| Secrets Accessibility | Secrets of the project are not accessible from the fork source | Accessible to project secrets |
| Workflow File Version | Uses the workflow version in the fork | Uses the workflow version in the target branch |
| Use Case | Secure code checks, linting, and building | Deployment, release, and commenting requiring write permissions |
| Default Checkout Source | PR pre-merged branch | Base branch |
Detailed Explanation of pull_request Event
# .gitcode/workflows/pr-check.yml
on:
pull_request:
branches: [main]
permissions:
repository: read # ATOMGIT_TOKEN has only read access under pull_request
pr: read # Cannot perform write operations on PR
stages:
check:
name: Code Check
jobs:
lint:
name: lint-and-test
runs-on: [ubuntu-latest, x64, small]
steps:
- name: Run lint
run: npm run lint
- name: Run npm
run: npm test
Security Mechanism:
- Code Source: Executes code from the fork repository's PR branch, including workflow files modified by the PR submitter.
- Permission Restrictions: ATOMGIT_TOKEN has only read permissions, unable to push code, modify PR, or operate project resources.
- Secret Isolation: Workflow from fork cannot access project-level and organization-level secrets.
Mechanism Effect: Malicious PR submitters can modify workflow file content, but due to lack of secrets and write permissions, the attack scope is limited — they cannot steal credentials or modify the repository.
Detailed Explanation of pull_request_target Event
# .gitcode/workflows/pr-build.yml
on:
pull_request_target:
branches: [main]
permissions:
repository: write # pull_request_target has write permissions
pr: write # Can comment/operate PR
stages:
build:
name: Build
jobs:
Build:
name: build-and-report
runs-on: [ubuntu-latest, x64, medium]
steps:
- name: Checkout source code
uses: checkout # By default, it checks out the default branch of the base repository
- name: build
run: make build
- name: Run script
run: |
curl -X POST "https://atomgit.com/api/v5/repos/${{ atomgit.repository }}/pulls/${{ atomgit.event.pull_request.number }}/comments" \
-H "Authorization: token $ATOMGIT_TOKEN" \
-d '{"body": "Build succeeded ✅"}'
Security Mechanism:
- Code Source: The workflow file uses the version from the target repository (main branch), not the version from the fork. This is the key difference — the PR submitter cannot modify the execution logic.
- Permission Scope: ATOMGIT_TOKEN has full permissions as declared, allowing writing to the repository and operating the PR.
- Secret Accessibility: Project-level and organization-level secrets are accessible.
Risks of pull_request_target Event
The workflow triggered by pull_request_target runs using the base repository's ATOMGIT_TOKEN and secrets. If it directly checks out fork code, attackers can inject malicious commands into Makefile, build scripts, etc., and use the base repository's credentials to execute or send private information through third-party requests, i.e., a "pwn request" attack.
Default Protection: In the pull_request_target scenario, the checkout plugin defaults to checking out the target branch of the base repository, not the PR branch. Since the target branch contains only trusted code and does not involve fork code, it is safe to grant sensitive information and read/write tokens.
Risk Scenario: Manually pointing the checkout to the PR source branch (e.g., ref: ${{ atomgit.event.pull_request.head.sha }}) breaks this protection, leading to fork code executing in the base repository's credential environment.
Similar vulnerable patterns also include: setting repository to the fork repository, using the commit SHA of the merged branch (e.g., ref: ${{ atomgit.event.merge_commit_sha }}), or using the merged branch (e.g., refs/merge-requests/N/merge).
Built-in Protection Mechanisms for Code Checkout Under pull_request_target Event
When the trigger scenario is pull_request_target and the source is a PR from a fork repository, for security reasons, the following checkouts are defaultly blocked:
| Scenario | Matching Dimension | Blocking Reason | Example |
|---|---|---|---|
| A. ref is the PR source branch or merged branch | ref matches refs/merge-requests/N/(head|merge) | Merged branch contains fork code, which may lead to "pwn request" attacks | ref: refs/merge-requests/13/merge |
| B. Repository Full Name Matches Fork Head | User-provided repository == pull_request.head.repo.full_name | Checking out code from any branch of the fork repository may lead to "pwn request" attacks | repository: .../i-am-fork-group/fork-test.git |
| C. Commit SHA Matches | commit is in the list [atomgit.event.pull_request.head.sha, atomgit.event.merge_commit_sha], which corresponds to the commit SHA of the PR source branch or merged branch | Checking out code using the commit SHA of the merged branch, which contains fork code, may lead to "pwn request" attacks | ref: bd16c8c2... |
Disabling Built-in Protection Mechanism Under pull_request_target Event
After reviewing the risks, set allow-unsafe-pr-checkout: true in the checkout step.
Note: Fully review the safety of the fork code in the PR context before checking out. The fork code will be executed in the base repository's token and secrets environment, which poses a security risk.
# .gitcode/workflows/pr-build.yml
on:
pull_request_target:
branches: [ main ]
permissions:
repository: write # pull_request_target has write permissions
pr: write # Can comment/operate PR
stages:
build:
name: Build
jobs:
Build:
name: build-and-report
runs-on: [ ubuntu-latest, x64, medium ]
steps:
- name: Checkout source code
uses: checkout
with:
allow-unsafe-pr-checkout: true # Explicitly configure to allow unsafe PR checkout
ref: refs/merge-requests/N/merge # After explicitly configuring allow-unsafe-pr-checkout: true, it allows checking out the merged branch
- name: build
run: make build
- name: Run script
run: |
curl -X POST "https://atomgit.com/api/v5/repos/${{ atomgit.repository }}/pulls/${{ atomgit.event.pull_request.number }}/comments" \
-H "Authorization: token $ATOMGIT_TOKEN" \
-d '{"body": "Build succeeded ✅"}'