From 5500d845a4b38a3839476d31adcea5ffa6b66dbf Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Thu, 16 Apr 2026 12:32:48 -0700 Subject: [PATCH 1/9] Use collaborator permission API instead of event payload author_association The webhook event payload's author_association field is unreliable for PRs originating from forks: even if the author is an org member or explicit collaborator with maintain/write permissions, fork PRs receive CONTRIBUTOR. This change queries the collaborator permission API directly to get the author's actual permission level (admin/maintain/write/triage/read/none), which is authoritative regardless of whether the PR comes from a fork or a branch in the main repo. Requires contents:write permission to access the collaborator API endpoint. Made-with: Cursor --- .github/workflows/restricted-paths-guard.yml | 30 +++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/.github/workflows/restricted-paths-guard.yml b/.github/workflows/restricted-paths-guard.yml index 7bfc4f29a6..49ed2fc86a 100644 --- a/.github/workflows/restricted-paths-guard.yml +++ b/.github/workflows/restricted-paths-guard.yml @@ -19,12 +19,13 @@ jobs: if: github.repository_owner == 'NVIDIA' runs-on: ubuntu-latest permissions: + contents: write # needed for collaborator permission check pull-requests: write steps: - name: Inspect PR author signals for restricted paths env: - # PR metadata inputs - AUTHOR_ASSOCIATION: ${{ github.event.pull_request.author_association || 'NONE' }} + # PR metadata inputs (author_association from event payload is + # unreliable for fork PRs, so we query the collaborator API directly) PR_AUTHOR: ${{ github.event.pull_request.user.login }} PR_NUMBER: ${{ github.event.pull_request.number }} PR_URL: ${{ github.event.pull_request.html_url }} @@ -38,6 +39,15 @@ jobs: run: | set -euo pipefail + # Query the collaborator permission API to get the author's actual + # permission level. This is authoritative regardless of whether the + # PR originates from a fork or a branch in the main repo. + # Returns: admin, maintain, write, triage, read, or none. + COLLABORATOR_PERMISSION=$( + gh api "repos/$REPO/collaborators/$PR_AUTHOR/permission" \ + --jq '.permission' 2>/dev/null || echo "none" + ) + if ! MATCHING_RESTRICTED_PATHS=$( gh api \ --paginate \ @@ -63,7 +73,7 @@ jobs: echo "" echo "- **Error**: Failed to inspect the PR file list." echo "- **Author**: $PR_AUTHOR" - echo "- **Author association**: $AUTHOR_ASSOCIATION" + echo "- **Collaborator permission**: $COLLABORATOR_PERMISSION" echo "" echo "Please update the PR at: $PR_URL" } >> "$GITHUB_STEP_SUMMARY" @@ -83,7 +93,7 @@ jobs: echo "" echo "- **Error**: Failed to inspect the current PR labels." echo "- **Author**: $PR_AUTHOR" - echo "- **Author association**: $AUTHOR_ASSOCIATION" + echo "- **Collaborator permission**: $COLLABORATOR_PERMISSION" echo "" echo "Please update the PR at: $PR_URL" } >> "$GITHUB_STEP_SUMMARY" @@ -107,11 +117,11 @@ jobs: TRUSTED_SIGNALS="(none)" if [ "$TOUCHES_RESTRICTED_PATHS" = "true" ]; then - case "$AUTHOR_ASSOCIATION" in - COLLABORATOR|MEMBER|OWNER) + case "$COLLABORATOR_PERMISSION" in + admin|maintain|write) HAS_TRUSTED_SIGNAL=true - LABEL_ACTION="not needed (author association is a trusted signal)" - TRUSTED_SIGNALS="author_association:$AUTHOR_ASSOCIATION" + LABEL_ACTION="not needed (collaborator permission is a trusted signal)" + TRUSTED_SIGNALS="collaborator_permission:$COLLABORATOR_PERMISSION" ;; esac fi @@ -136,7 +146,7 @@ jobs: echo "" echo "- **Error**: Failed to add the \`$REVIEW_LABEL\` label." echo "- **Author**: $PR_AUTHOR" - echo "- **Author association**: $AUTHOR_ASSOCIATION" + echo "- **Collaborator permission**: $COLLABORATOR_PERMISSION" echo "" write_matching_restricted_paths echo "" @@ -154,7 +164,7 @@ jobs: echo "## Restricted Paths Guard Completed" echo "" echo "- **Author**: $PR_AUTHOR" - echo "- **Author association**: $AUTHOR_ASSOCIATION" + echo "- **Collaborator permission**: $COLLABORATOR_PERMISSION" echo "- **Touches restricted paths**: $TOUCHES_RESTRICTED_PATHS" echo "- **Restricted paths**: \`cuda_bindings/\`, \`cuda_python/\`" echo "- **Trusted signals**: $TRUSTED_SIGNALS" From b814323f08a32cc4e4427858488937ba8253b182 Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Thu, 16 Apr 2026 12:35:26 -0700 Subject: [PATCH 2/9] TEMPORARY: Switch to pull_request trigger for testing This commit is for testing the collaborator permission check and must be reverted before merge: 1. Changes trigger from pull_request_target to pull_request so this branch's workflow definition runs instead of main's. 2. Adds a dummy change to cuda_bindings/pyproject.toml to trigger the restricted-paths detection. REVERT THIS COMMIT BEFORE MERGE. Made-with: Cursor --- .github/workflows/restricted-paths-guard.yml | 3 ++- cuda_bindings/pyproject.toml | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/restricted-paths-guard.yml b/.github/workflows/restricted-paths-guard.yml index 49ed2fc86a..524da27ec7 100644 --- a/.github/workflows/restricted-paths-guard.yml +++ b/.github/workflows/restricted-paths-guard.yml @@ -6,7 +6,8 @@ name: "CI: Restricted Paths Guard" on: # Run on drafts too so maintainers get early awareness on WIP PRs. # Label updates on fork PRs require pull_request_target permissions. - pull_request_target: + # TEMPORARY: Using pull_request for testing; revert to pull_request_target before merge. + pull_request: types: - opened - synchronize diff --git a/cuda_bindings/pyproject.toml b/cuda_bindings/pyproject.toml index 3aa9c62556..ad890b45f6 100644 --- a/cuda_bindings/pyproject.toml +++ b/cuda_bindings/pyproject.toml @@ -1,5 +1,6 @@ # SPDX-FileCopyrightText: Copyright (c) 2023-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: LicenseRef-NVIDIA-SOFTWARE-LICENSE +# XXX DUMMY CHANGE FOR TESTING restricted-paths-guard.yml - REMOVE BEFORE MERGE XXX [build-system] requires = [ "setuptools>=80.0.0", From 4898bddf5d1b7867ab721693b9370613d3f8c1bc Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Thu, 16 Apr 2026 12:58:11 -0700 Subject: [PATCH 3/9] Revert "TEMPORARY: Switch to pull_request trigger for testing" This reverts commit b814323f08a32cc4e4427858488937ba8253b182. --- .github/workflows/restricted-paths-guard.yml | 3 +-- cuda_bindings/pyproject.toml | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/restricted-paths-guard.yml b/.github/workflows/restricted-paths-guard.yml index 524da27ec7..49ed2fc86a 100644 --- a/.github/workflows/restricted-paths-guard.yml +++ b/.github/workflows/restricted-paths-guard.yml @@ -6,8 +6,7 @@ name: "CI: Restricted Paths Guard" on: # Run on drafts too so maintainers get early awareness on WIP PRs. # Label updates on fork PRs require pull_request_target permissions. - # TEMPORARY: Using pull_request for testing; revert to pull_request_target before merge. - pull_request: + pull_request_target: types: - opened - synchronize diff --git a/cuda_bindings/pyproject.toml b/cuda_bindings/pyproject.toml index ad890b45f6..3aa9c62556 100644 --- a/cuda_bindings/pyproject.toml +++ b/cuda_bindings/pyproject.toml @@ -1,6 +1,5 @@ # SPDX-FileCopyrightText: Copyright (c) 2023-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: LicenseRef-NVIDIA-SOFTWARE-LICENSE -# XXX DUMMY CHANGE FOR TESTING restricted-paths-guard.yml - REMOVE BEFORE MERGE XXX [build-system] requires = [ "setuptools>=80.0.0", From 45b83571219b500622fd1b941dfd11db70192766 Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Fri, 17 Apr 2026 07:20:12 -0700 Subject: [PATCH 4/9] Add explicit handling for non-trusted permission levels Address review feedback: explicitly handle the fallthrough case in the permission check to make it clear that triage, read, none, and API errors are not trusted signals. Made-with: Cursor --- .github/workflows/restricted-paths-guard.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/restricted-paths-guard.yml b/.github/workflows/restricted-paths-guard.yml index 49ed2fc86a..0750ceee95 100644 --- a/.github/workflows/restricted-paths-guard.yml +++ b/.github/workflows/restricted-paths-guard.yml @@ -123,6 +123,9 @@ jobs: LABEL_ACTION="not needed (collaborator permission is a trusted signal)" TRUSTED_SIGNALS="collaborator_permission:$COLLABORATOR_PERMISSION" ;; + *) + # triage, read, none, or API error: not a trusted signal + ;; esac fi From 8686adb4be9f702b393358a87a95d755bd0b217f Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Thu, 16 Apr 2026 12:35:26 -0700 Subject: [PATCH 5/9] TEMPORARY: Switch to pull_request trigger for testing This commit is for testing the collaborator permission check and must be reverted before merge: 1. Changes trigger from pull_request_target to pull_request so this branch's workflow definition runs instead of main's. 2. Adds a dummy change to cuda_bindings/pyproject.toml to trigger the restricted-paths detection. REVERT THIS COMMIT BEFORE MERGE. Made-with: Cursor --- .github/workflows/restricted-paths-guard.yml | 3 ++- cuda_bindings/pyproject.toml | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/restricted-paths-guard.yml b/.github/workflows/restricted-paths-guard.yml index 0750ceee95..ddfb95ad99 100644 --- a/.github/workflows/restricted-paths-guard.yml +++ b/.github/workflows/restricted-paths-guard.yml @@ -6,7 +6,8 @@ name: "CI: Restricted Paths Guard" on: # Run on drafts too so maintainers get early awareness on WIP PRs. # Label updates on fork PRs require pull_request_target permissions. - pull_request_target: + # TEMPORARY: Using pull_request for testing; revert to pull_request_target before merge. + pull_request: types: - opened - synchronize diff --git a/cuda_bindings/pyproject.toml b/cuda_bindings/pyproject.toml index 3aa9c62556..ad890b45f6 100644 --- a/cuda_bindings/pyproject.toml +++ b/cuda_bindings/pyproject.toml @@ -1,5 +1,6 @@ # SPDX-FileCopyrightText: Copyright (c) 2023-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: LicenseRef-NVIDIA-SOFTWARE-LICENSE +# XXX DUMMY CHANGE FOR TESTING restricted-paths-guard.yml - REMOVE BEFORE MERGE XXX [build-system] requires = [ "setuptools>=80.0.0", From f5aa8a1e9428402b364a00feee879e6ebaa5e271 Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Fri, 17 Apr 2026 07:23:38 -0700 Subject: [PATCH 6/9] Revert "TEMPORARY: Switch to pull_request trigger for testing" This reverts commit 8686adb4be9f702b393358a87a95d755bd0b217f. --- .github/workflows/restricted-paths-guard.yml | 3 +-- cuda_bindings/pyproject.toml | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/restricted-paths-guard.yml b/.github/workflows/restricted-paths-guard.yml index ddfb95ad99..0750ceee95 100644 --- a/.github/workflows/restricted-paths-guard.yml +++ b/.github/workflows/restricted-paths-guard.yml @@ -6,8 +6,7 @@ name: "CI: Restricted Paths Guard" on: # Run on drafts too so maintainers get early awareness on WIP PRs. # Label updates on fork PRs require pull_request_target permissions. - # TEMPORARY: Using pull_request for testing; revert to pull_request_target before merge. - pull_request: + pull_request_target: types: - opened - synchronize diff --git a/cuda_bindings/pyproject.toml b/cuda_bindings/pyproject.toml index ad890b45f6..3aa9c62556 100644 --- a/cuda_bindings/pyproject.toml +++ b/cuda_bindings/pyproject.toml @@ -1,6 +1,5 @@ # SPDX-FileCopyrightText: Copyright (c) 2023-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: LicenseRef-NVIDIA-SOFTWARE-LICENSE -# XXX DUMMY CHANGE FOR TESTING restricted-paths-guard.yml - REMOVE BEFORE MERGE XXX [build-system] requires = [ "setuptools>=80.0.0", From 500cb298731f3c4feb752c33dddff6b33b547a87 Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Sat, 18 Apr 2026 09:52:10 -0700 Subject: [PATCH 7/9] Fail restricted-paths guard on collaborator API errors Treat 404 responses from the collaborator permission API as the expected non-collaborator case, but fail the workflow for any other API error so restricted-paths review labels are not added based on an unknown result. Made-with: Cursor --- .github/workflows/restricted-paths-guard.yml | 49 ++++++++++++++++---- 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/.github/workflows/restricted-paths-guard.yml b/.github/workflows/restricted-paths-guard.yml index 0750ceee95..8d7e6eccd4 100644 --- a/.github/workflows/restricted-paths-guard.yml +++ b/.github/workflows/restricted-paths-guard.yml @@ -39,14 +39,8 @@ jobs: run: | set -euo pipefail - # Query the collaborator permission API to get the author's actual - # permission level. This is authoritative regardless of whether the - # PR originates from a fork or a branch in the main repo. - # Returns: admin, maintain, write, triage, read, or none. - COLLABORATOR_PERMISSION=$( - gh api "repos/$REPO/collaborators/$PR_AUTHOR/permission" \ - --jq '.permission' 2>/dev/null || echo "none" - ) + COLLABORATOR_PERMISSION="not checked" + COLLABORATOR_PERMISSION_API_ERROR="" if ! MATCHING_RESTRICTED_PATHS=$( gh api \ @@ -112,11 +106,48 @@ jobs: echo '```' } + write_collaborator_permission_api_error() { + echo "- **Collaborator permission API error**:" + echo '```text' + printf '%s\n' "$COLLABORATOR_PERMISSION_API_ERROR" + echo '```' + } + HAS_TRUSTED_SIGNAL=false LABEL_ACTION="not needed (no restricted paths)" TRUSTED_SIGNALS="(none)" if [ "$TOUCHES_RESTRICTED_PATHS" = "true" ]; then + # Distinguish a legitimate 404 "not a collaborator" response from + # actual API failures. The former is an expected untrusted case; + # the latter fails the workflow so it can be rerun later. + if COLLABORATOR_PERMISSION_RESPONSE=$( + gh api "repos/$REPO/collaborators/$PR_AUTHOR/permission" \ + --jq '.permission' 2>&1 + ); then + COLLABORATOR_PERMISSION="$COLLABORATOR_PERMISSION_RESPONSE" + elif [[ "$COLLABORATOR_PERMISSION_RESPONSE" == *"(HTTP 404)"* ]]; then + COLLABORATOR_PERMISSION="none" + else + COLLABORATOR_PERMISSION="unknown" + COLLABORATOR_PERMISSION_API_ERROR="$COLLABORATOR_PERMISSION_RESPONSE" + echo "::error::Failed to inspect collaborator permission for $PR_AUTHOR." + { + echo "## Restricted Paths Guard Failed" + echo "" + echo "- **Error**: Failed to inspect collaborator permission." + echo "- **Author**: $PR_AUTHOR" + echo "- **Collaborator permission**: $COLLABORATOR_PERMISSION" + echo "" + write_matching_restricted_paths + echo "" + write_collaborator_permission_api_error + echo "" + echo "Please retry this workflow. If the failure persists, inspect the collaborator permission API error above." + } >> "$GITHUB_STEP_SUMMARY" + exit 1 + fi + case "$COLLABORATOR_PERMISSION" in admin|maintain|write) HAS_TRUSTED_SIGNAL=true @@ -124,7 +155,7 @@ jobs: TRUSTED_SIGNALS="collaborator_permission:$COLLABORATOR_PERMISSION" ;; *) - # triage, read, none, or API error: not a trusted signal + # triage, read, or none: not a trusted signal ;; esac fi From 2a019b6e14ffa214e87a541d7f9df1d66a525f62 Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Thu, 16 Apr 2026 12:35:26 -0700 Subject: [PATCH 8/9] TEMPORARY: Switch to pull_request trigger for testing This commit is for testing the collaborator permission check and must be reverted before merge: 1. Changes trigger from pull_request_target to pull_request so this branch's workflow definition runs instead of main's. 2. Adds a dummy change to cuda_bindings/pyproject.toml to trigger the restricted-paths detection. REVERT THIS COMMIT BEFORE MERGE. Made-with: Cursor --- .github/workflows/restricted-paths-guard.yml | 3 ++- cuda_bindings/pyproject.toml | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/restricted-paths-guard.yml b/.github/workflows/restricted-paths-guard.yml index 8d7e6eccd4..3084046e96 100644 --- a/.github/workflows/restricted-paths-guard.yml +++ b/.github/workflows/restricted-paths-guard.yml @@ -6,7 +6,8 @@ name: "CI: Restricted Paths Guard" on: # Run on drafts too so maintainers get early awareness on WIP PRs. # Label updates on fork PRs require pull_request_target permissions. - pull_request_target: + # TEMPORARY: Using pull_request for testing; revert to pull_request_target before merge. + pull_request: types: - opened - synchronize diff --git a/cuda_bindings/pyproject.toml b/cuda_bindings/pyproject.toml index 3aa9c62556..ad890b45f6 100644 --- a/cuda_bindings/pyproject.toml +++ b/cuda_bindings/pyproject.toml @@ -1,5 +1,6 @@ # SPDX-FileCopyrightText: Copyright (c) 2023-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: LicenseRef-NVIDIA-SOFTWARE-LICENSE +# XXX DUMMY CHANGE FOR TESTING restricted-paths-guard.yml - REMOVE BEFORE MERGE XXX [build-system] requires = [ "setuptools>=80.0.0", From 4943745032eb535ac8942dcd4300e4406c71a9a5 Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Sat, 18 Apr 2026 10:10:35 -0700 Subject: [PATCH 9/9] Revert "TEMPORARY: Switch to pull_request trigger for testing" This reverts commit 2a019b6e14ffa214e87a541d7f9df1d66a525f62. --- .github/workflows/restricted-paths-guard.yml | 3 +-- cuda_bindings/pyproject.toml | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/restricted-paths-guard.yml b/.github/workflows/restricted-paths-guard.yml index 3084046e96..8d7e6eccd4 100644 --- a/.github/workflows/restricted-paths-guard.yml +++ b/.github/workflows/restricted-paths-guard.yml @@ -6,8 +6,7 @@ name: "CI: Restricted Paths Guard" on: # Run on drafts too so maintainers get early awareness on WIP PRs. # Label updates on fork PRs require pull_request_target permissions. - # TEMPORARY: Using pull_request for testing; revert to pull_request_target before merge. - pull_request: + pull_request_target: types: - opened - synchronize diff --git a/cuda_bindings/pyproject.toml b/cuda_bindings/pyproject.toml index ad890b45f6..3aa9c62556 100644 --- a/cuda_bindings/pyproject.toml +++ b/cuda_bindings/pyproject.toml @@ -1,6 +1,5 @@ # SPDX-FileCopyrightText: Copyright (c) 2023-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: LicenseRef-NVIDIA-SOFTWARE-LICENSE -# XXX DUMMY CHANGE FOR TESTING restricted-paths-guard.yml - REMOVE BEFORE MERGE XXX [build-system] requires = [ "setuptools>=80.0.0",