From 476581ccde7d596ded05d2b77b36edb57762a2c4 Mon Sep 17 00:00:00 2001 From: Michael Recachinas Date: Tue, 11 Aug 2026 10:58:35 -0400 Subject: [PATCH 1/2] Migrate close-on-PR workflow off pull_request_target Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/close-invalid-pr-writer.yml | 51 +++++++++++++++++++ .github/workflows/close-invalid.yml | 46 +++++++++-------- 2 files changed, 76 insertions(+), 21 deletions(-) create mode 100644 .github/workflows/close-invalid-pr-writer.yml diff --git a/.github/workflows/close-invalid-pr-writer.yml b/.github/workflows/close-invalid-pr-writer.yml new file mode 100644 index 00000000..7cfc6a70 --- /dev/null +++ b/.github/workflows/close-invalid-pr-writer.yml @@ -0,0 +1,51 @@ +name: Close invalid PR writer + +on: + workflow_run: + workflows: [Close issue/PR on adding invalid label] + types: [completed] + +permissions: + issues: read + pull-requests: write + +jobs: + close-invalid-pr: + if: > + github.repository == 'github/copilot-cli' && + github.event.workflow_run.event == 'pull_request' && + github.event.workflow_run.conclusion == 'success' && + github.event.workflow_run.repository.full_name == github.repository + runs-on: ubuntu-latest + steps: + - name: Close invalid PR + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + HEAD_OWNER: ${{ github.event.workflow_run.head_repository.owner.login }} + HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }} + PR_NUMBER: ${{ github.event.workflow_run.pull_requests[0].number }} + run: | + set -euo pipefail + + if [ -z "${PR_NUMBER:-}" ] || [ "$PR_NUMBER" = "null" ]; then + 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')" + fi + + if [ -z "${PR_NUMBER:-}" ]; then + echo "Unable to identify a single open PR for workflow run; skipping." + exit 0 + fi + + pr_state="$(gh api "repos/$GH_REPO/pulls/$PR_NUMBER" --jq .state)" + if [ "$pr_state" != "open" ]; then + echo "PR #$PR_NUMBER is $pr_state; skipping." + exit 0 + fi + + if ! gh api "repos/$GH_REPO/issues/$PR_NUMBER/labels" --jq '.[].name' | grep -Fxq invalid; then + echo "PR #$PR_NUMBER does not currently have the invalid label; skipping." + exit 0 + fi + + gh api -X PATCH "repos/$GH_REPO/pulls/$PR_NUMBER" -f state=closed diff --git a/.github/workflows/close-invalid.yml b/.github/workflows/close-invalid.yml index 4078bd87..b1fa4801 100644 --- a/.github/workflows/close-invalid.yml +++ b/.github/workflows/close-invalid.yml @@ -1,36 +1,40 @@ name: Close issue/PR on adding invalid label -# **What it does**: This action closes issues that are labeled as invalid in the repo. +# **What it does**: This action closes issues and PRs that are labeled as invalid in the repo. on: issues: types: [labeled] - pull_request_target: + pull_request: types: [labeled] -permissions: - contents: read - issues: write - pull-requests: write +permissions: {} jobs: - close-on-adding-invalid-label: - if: - github.repository == 'github/copilot-cli' && github.event.label.name == - 'invalid' + close-issue-on-adding-invalid-label: + if: > + github.repository == 'github/copilot-cli' && + github.event_name == 'issues' && + github.event.label.name == 'invalid' runs-on: ubuntu-latest - + permissions: + issues: write steps: - name: Close issue - if: ${{ github.event_name == 'issues' }} env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - URL: ${{ github.event.issue.html_url }} - run: gh issue close $URL + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPOSITORY: ${{ github.repository }} + ISSUE_NUMBER: ${{ github.event.issue.number }} + run: gh api -X PATCH "repos/$GH_REPOSITORY/issues/$ISSUE_NUMBER" -f state=closed - - name: Close PR - if: ${{ github.event_name == 'pull_request_target' }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - URL: ${{ github.event.pull_request.html_url }} - run: gh pr close $URL + signal-invalid-pr-label: + if: > + github.repository == 'github/copilot-cli' && + github.event_name == 'pull_request' && + github.event.label.name == 'invalid' + runs-on: ubuntu-latest + permissions: + pull-requests: read + steps: + - name: Record invalid PR label signal + run: echo "Invalid label signal for PR #${{ github.event.pull_request.number }}" From 132979e761a234a7061126488c1d4f7e2960c076 Mon Sep 17 00:00:00 2001 From: Michael Recachinas Date: Wed, 12 Aug 2026 09:51:29 -0400 Subject: [PATCH 2/2] 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 --- .github/workflows/close-invalid-pr-writer.yml | 85 +++++++++++++++---- .github/workflows/close-invalid.yml | 10 ++- 2 files changed, 73 insertions(+), 22 deletions(-) diff --git a/.github/workflows/close-invalid-pr-writer.yml b/.github/workflows/close-invalid-pr-writer.yml index 7cfc6a70..1094d6f4 100644 --- a/.github/workflows/close-invalid-pr-writer.yml +++ b/.github/workflows/close-invalid-pr-writer.yml @@ -4,48 +4,97 @@ on: workflow_run: workflows: [Close issue/PR on adding invalid label] types: [completed] + # pull_request does not run for conflicted PRs, so reconcile from the trusted default branch. + schedule: + - cron: '*/5 * * * *' + workflow_dispatch: -permissions: - issues: read - pull-requests: write +permissions: {} jobs: - close-invalid-pr: + close-invalid-pr-from-workflow-run: if: > github.repository == 'github/copilot-cli' && + github.event_name == 'workflow_run' && github.event.workflow_run.event == 'pull_request' && - github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.repository.full_name == github.repository runs-on: ubuntu-latest + permissions: + actions: read + pull-requests: write + concurrency: + group: close-invalid-pr-${{ github.event.workflow_run.pull_requests[0].number || github.run_id }} + cancel-in-progress: false steps: - name: Close invalid PR env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_REPO: ${{ github.repository }} - HEAD_OWNER: ${{ github.event.workflow_run.head_repository.owner.login }} - HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }} - PR_NUMBER: ${{ github.event.workflow_run.pull_requests[0].number }} + WORKFLOW_RUN_ID: ${{ github.event.workflow_run.id }} run: | set -euo pipefail - if [ -z "${PR_NUMBER:-}" ] || [ "$PR_NUMBER" = "null" ]; then - 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')" + trusted_workflow_id="$(gh api "repos/$GH_REPO/actions/workflows/close-invalid.yml" --jq .id)" + workflow_run="$(gh api "repos/$GH_REPO/actions/runs/$WORKFLOW_RUN_ID")" + + if [ "$(jq -r .workflow_id <<<"$workflow_run")" != "$trusted_workflow_id" ] || + [ "$(jq -r .event <<<"$workflow_run")" != "pull_request" ] || + [ "$(jq -r .repository.full_name <<<"$workflow_run")" != "$GH_REPO" ]; then + echo "Workflow run is not a trusted pull_request run from $GH_REPO; skipping." + exit 0 fi - if [ -z "${PR_NUMBER:-}" ]; then - echo "Unable to identify a single open PR for workflow run; skipping." + if [ "$(jq '.pull_requests | length' <<<"$workflow_run")" -ne 1 ]; then + echo "Workflow run is not associated with exactly one PR; skipping." exit 0 fi - pr_state="$(gh api "repos/$GH_REPO/pulls/$PR_NUMBER" --jq .state)" - if [ "$pr_state" != "open" ]; then - echo "PR #$PR_NUMBER is $pr_state; skipping." + pr_number="$(jq -r .pull_requests[0].number <<<"$workflow_run")" + run_head_sha="$(jq -r .head_sha <<<"$workflow_run")" + run_head_repo="$(jq -r '.head_repository.full_name // empty' <<<"$workflow_run")" + pr="$(gh api "repos/$GH_REPO/pulls/$pr_number")" + + if [ -z "$run_head_repo" ] || + [ "$(jq -r .base.repo.full_name <<<"$pr")" != "$GH_REPO" ] || + [ "$(jq -r '.head.repo.full_name // empty' <<<"$pr")" != "$run_head_repo" ] || + [ "$(jq -r .head.sha <<<"$pr")" != "$run_head_sha" ]; then + echo "PR #$pr_number no longer matches the workflow run head; skipping." exit 0 fi - if ! gh api "repos/$GH_REPO/issues/$PR_NUMBER/labels" --jq '.[].name' | grep -Fxq invalid; then - echo "PR #$PR_NUMBER does not currently have the invalid label; skipping." + if [ "$(jq -r .state <<<"$pr")" != "open" ] || + ! jq -e 'any(.labels[]?; .name == "invalid")' >/dev/null <<<"$pr"; then + echo "PR #$pr_number is not open with the invalid label; skipping." exit 0 fi - gh api -X PATCH "repos/$GH_REPO/pulls/$PR_NUMBER" -f state=closed + gh api -X PATCH "repos/$GH_REPO/pulls/$pr_number" -f state=closed + + reconcile-invalid-prs: + if: > + github.repository == 'github/copilot-cli' && + (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') + runs-on: ubuntu-latest + permissions: + pull-requests: write + concurrency: + group: close-invalid-pr-reconciliation + cancel-in-progress: false + steps: + - name: Close open PRs with the invalid label + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + run: | + set -euo pipefail + + gh api --paginate "repos/$GH_REPO/pulls?state=open&per_page=100" \ + --jq '.[] | select(any(.labels[]?; .name == "invalid")) | .number' | + while read -r pr_number; do + pr="$(gh api "repos/$GH_REPO/pulls/$pr_number")" + + if [ "$(jq -r .state <<<"$pr")" = "open" ] && + jq -e 'any(.labels[]?; .name == "invalid")' >/dev/null <<<"$pr"; then + gh api -X PATCH "repos/$GH_REPO/pulls/$pr_number" -f state=closed + fi + done diff --git a/.github/workflows/close-invalid.yml b/.github/workflows/close-invalid.yml index b1fa4801..18288ff0 100644 --- a/.github/workflows/close-invalid.yml +++ b/.github/workflows/close-invalid.yml @@ -1,6 +1,6 @@ name: Close issue/PR on adding invalid label -# **What it does**: This action closes issues and PRs that are labeled as invalid in the repo. +# **What it does**: This action closes invalid issues and signals invalid PRs to a trusted writer. on: issues: @@ -33,8 +33,10 @@ jobs: github.event_name == 'pull_request' && github.event.label.name == 'invalid' runs-on: ubuntu-latest - permissions: - pull-requests: read + permissions: {} steps: - name: Record invalid PR label signal - run: echo "Invalid label signal for PR #${{ github.event.pull_request.number }}" + env: + PR_NUMBER: ${{ github.event.pull_request.number }} + run: | + echo "Invalid label signal for PR #$PR_NUMBER"