Skip to content

Commit 132979e

Browse files
mrecachinasCopilot
andcommitted
Harden invalid PR close dispatcher
Bind privileged closure to the trusted workflow identity and exact PR head, and reconcile invalid conflicted PRs from the default branch. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 512eb347-ec89-4250-8bf1-87048974b01d
1 parent 476581c commit 132979e

2 files changed

Lines changed: 73 additions & 22 deletions

File tree

.github/workflows/close-invalid-pr-writer.yml

Lines changed: 67 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,48 +4,97 @@ on:
44
workflow_run:
55
workflows: [Close issue/PR on adding invalid label]
66
types: [completed]
7+
# pull_request does not run for conflicted PRs, so reconcile from the trusted default branch.
8+
schedule:
9+
- cron: '*/5 * * * *'
10+
workflow_dispatch:
711

8-
permissions:
9-
issues: read
10-
pull-requests: write
12+
permissions: {}
1113

1214
jobs:
13-
close-invalid-pr:
15+
close-invalid-pr-from-workflow-run:
1416
if: >
1517
github.repository == 'github/copilot-cli' &&
18+
github.event_name == 'workflow_run' &&
1619
github.event.workflow_run.event == 'pull_request' &&
17-
github.event.workflow_run.conclusion == 'success' &&
1820
github.event.workflow_run.repository.full_name == github.repository
1921
runs-on: ubuntu-latest
22+
permissions:
23+
actions: read
24+
pull-requests: write
25+
concurrency:
26+
group: close-invalid-pr-${{ github.event.workflow_run.pull_requests[0].number || github.run_id }}
27+
cancel-in-progress: false
2028
steps:
2129
- name: Close invalid PR
2230
env:
2331
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
2432
GH_REPO: ${{ github.repository }}
25-
HEAD_OWNER: ${{ github.event.workflow_run.head_repository.owner.login }}
26-
HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }}
27-
PR_NUMBER: ${{ github.event.workflow_run.pull_requests[0].number }}
33+
WORKFLOW_RUN_ID: ${{ github.event.workflow_run.id }}
2834
run: |
2935
set -euo pipefail
3036
31-
if [ -z "${PR_NUMBER:-}" ] || [ "$PR_NUMBER" = "null" ]; then
32-
PR_NUMBER="$(gh api --method GET "repos/$GH_REPO/pulls" -f state=open -f head="$HEAD_OWNER:$HEAD_BRANCH" --jq 'if length == 1 then .[0].number else empty end')"
37+
trusted_workflow_id="$(gh api "repos/$GH_REPO/actions/workflows/close-invalid.yml" --jq .id)"
38+
workflow_run="$(gh api "repos/$GH_REPO/actions/runs/$WORKFLOW_RUN_ID")"
39+
40+
if [ "$(jq -r .workflow_id <<<"$workflow_run")" != "$trusted_workflow_id" ] ||
41+
[ "$(jq -r .event <<<"$workflow_run")" != "pull_request" ] ||
42+
[ "$(jq -r .repository.full_name <<<"$workflow_run")" != "$GH_REPO" ]; then
43+
echo "Workflow run is not a trusted pull_request run from $GH_REPO; skipping."
44+
exit 0
3345
fi
3446
35-
if [ -z "${PR_NUMBER:-}" ]; then
36-
echo "Unable to identify a single open PR for workflow run; skipping."
47+
if [ "$(jq '.pull_requests | length' <<<"$workflow_run")" -ne 1 ]; then
48+
echo "Workflow run is not associated with exactly one PR; skipping."
3749
exit 0
3850
fi
3951
40-
pr_state="$(gh api "repos/$GH_REPO/pulls/$PR_NUMBER" --jq .state)"
41-
if [ "$pr_state" != "open" ]; then
42-
echo "PR #$PR_NUMBER is $pr_state; skipping."
52+
pr_number="$(jq -r .pull_requests[0].number <<<"$workflow_run")"
53+
run_head_sha="$(jq -r .head_sha <<<"$workflow_run")"
54+
run_head_repo="$(jq -r '.head_repository.full_name // empty' <<<"$workflow_run")"
55+
pr="$(gh api "repos/$GH_REPO/pulls/$pr_number")"
56+
57+
if [ -z "$run_head_repo" ] ||
58+
[ "$(jq -r .base.repo.full_name <<<"$pr")" != "$GH_REPO" ] ||
59+
[ "$(jq -r '.head.repo.full_name // empty' <<<"$pr")" != "$run_head_repo" ] ||
60+
[ "$(jq -r .head.sha <<<"$pr")" != "$run_head_sha" ]; then
61+
echo "PR #$pr_number no longer matches the workflow run head; skipping."
4362
exit 0
4463
fi
4564
46-
if ! gh api "repos/$GH_REPO/issues/$PR_NUMBER/labels" --jq '.[].name' | grep -Fxq invalid; then
47-
echo "PR #$PR_NUMBER does not currently have the invalid label; skipping."
65+
if [ "$(jq -r .state <<<"$pr")" != "open" ] ||
66+
! jq -e 'any(.labels[]?; .name == "invalid")' >/dev/null <<<"$pr"; then
67+
echo "PR #$pr_number is not open with the invalid label; skipping."
4868
exit 0
4969
fi
5070
51-
gh api -X PATCH "repos/$GH_REPO/pulls/$PR_NUMBER" -f state=closed
71+
gh api -X PATCH "repos/$GH_REPO/pulls/$pr_number" -f state=closed
72+
73+
reconcile-invalid-prs:
74+
if: >
75+
github.repository == 'github/copilot-cli' &&
76+
(github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')
77+
runs-on: ubuntu-latest
78+
permissions:
79+
pull-requests: write
80+
concurrency:
81+
group: close-invalid-pr-reconciliation
82+
cancel-in-progress: false
83+
steps:
84+
- name: Close open PRs with the invalid label
85+
env:
86+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
87+
GH_REPO: ${{ github.repository }}
88+
run: |
89+
set -euo pipefail
90+
91+
gh api --paginate "repos/$GH_REPO/pulls?state=open&per_page=100" \
92+
--jq '.[] | select(any(.labels[]?; .name == "invalid")) | .number' |
93+
while read -r pr_number; do
94+
pr="$(gh api "repos/$GH_REPO/pulls/$pr_number")"
95+
96+
if [ "$(jq -r .state <<<"$pr")" = "open" ] &&
97+
jq -e 'any(.labels[]?; .name == "invalid")' >/dev/null <<<"$pr"; then
98+
gh api -X PATCH "repos/$GH_REPO/pulls/$pr_number" -f state=closed
99+
fi
100+
done

.github/workflows/close-invalid.yml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: Close issue/PR on adding invalid label
22

3-
# **What it does**: This action closes issues and PRs that are labeled as invalid in the repo.
3+
# **What it does**: This action closes invalid issues and signals invalid PRs to a trusted writer.
44

55
on:
66
issues:
@@ -33,8 +33,10 @@ jobs:
3333
github.event_name == 'pull_request' &&
3434
github.event.label.name == 'invalid'
3535
runs-on: ubuntu-latest
36-
permissions:
37-
pull-requests: read
36+
permissions: {}
3837
steps:
3938
- name: Record invalid PR label signal
40-
run: echo "Invalid label signal for PR #${{ github.event.pull_request.number }}"
39+
env:
40+
PR_NUMBER: ${{ github.event.pull_request.number }}
41+
run: |
42+
echo "Invalid label signal for PR #$PR_NUMBER"

0 commit comments

Comments
 (0)