Summary
The check-scope job in .github/workflows/semantic-pr.yml runs under the pull_request_target trigger, checks out fork-controlled code (github.event.pull_request.head.sha), and then executes a script from that checkout (bash .github/scripts/classify-pr-files.sh). This is a classic "pwn request" pattern: the job carries the base repository's GITHUB_TOKEN (with pull-requests: write) and secrets, so a malicious fork PR can replace classify-pr-files.sh with arbitrary code to exfiltrate the token or abuse write permissions.
This was surfaced by the bump of actions/checkout to v7 (#551), which now refuses to check out fork PR code by default under pull_request_target/workflow_run.
Impact
- Security: fork PRs can run arbitrary code with the base repo token + secrets (pwn request).
- Functional (post-v7): once checkout v7 lands,
check-scope will fail for PRs originating from forks, because checkout refuses the unsafe fork checkout by default.
Affected code
.github/workflows/semantic-pr.yml:
on:
pull_request_target:
types: [opened, edited, synchronize]
...
check-scope:
steps:
- uses: actions/checkout@...
with:
ref: ${{ github.event.pull_request.head.sha }} # fork code
fetch-depth: 0
- run: bash .github/scripts/classify-pr-files.sh # executes fork's copy
Proposed fix (do NOT just opt back in)
Avoid executing fork-controlled code. Preferred options, in order:
- Never run fork code. Check out the base ref (omit
ref:) so the trusted classify-pr-files.sh runs, then fetch the PR head and compute the diff as data only:
git fetch origin <head.sha> then git diff <base.sha>...<head.sha> --name-only.
- The script only reads file names, so it never needs fork file contents executed.
- Switch the trigger to plain
pull_request and split the comment-posting (which needs write) into a separate step that does not check out fork code.
Explicitly not recommended: setting allow-unsafe-pr-checkout: true — that re-enables the exact vulnerability.
Notes
- The
lint-title job in the same workflow is fine (no ref:, runs base code).
cla.yml (pull_request_target, no checkout) and cleanup-artifacts.yml (schedule, no fork checkout) are unaffected.
- See GitHub's guidance: https://gh.io/securely-using-pull_request_target
Summary
The
check-scopejob in.github/workflows/semantic-pr.ymlruns under thepull_request_targettrigger, checks out fork-controlled code (github.event.pull_request.head.sha), and then executes a script from that checkout (bash .github/scripts/classify-pr-files.sh). This is a classic "pwn request" pattern: the job carries the base repository'sGITHUB_TOKEN(withpull-requests: write) and secrets, so a malicious fork PR can replaceclassify-pr-files.shwith arbitrary code to exfiltrate the token or abuse write permissions.This was surfaced by the bump of
actions/checkoutto v7 (#551), which now refuses to check out fork PR code by default underpull_request_target/workflow_run.Impact
check-scopewill fail for PRs originating from forks, because checkout refuses the unsafe fork checkout by default.Affected code
.github/workflows/semantic-pr.yml:Proposed fix (do NOT just opt back in)
Avoid executing fork-controlled code. Preferred options, in order:
ref:) so the trustedclassify-pr-files.shruns, then fetch the PR head and compute the diff as data only:git fetch origin <head.sha>thengit diff <base.sha>...<head.sha> --name-only.pull_requestand split the comment-posting (which needs write) into a separate step that does not check out fork code.Explicitly not recommended: setting
allow-unsafe-pr-checkout: true— that re-enables the exact vulnerability.Notes
lint-titlejob in the same workflow is fine (noref:, runs base code).cla.yml(pull_request_target, no checkout) andcleanup-artifacts.yml(schedule, no fork checkout) are unaffected.