From f370df92ae7b447af6c1e20494c0747ba6b518dc Mon Sep 17 00:00:00 2001 From: enyst Date: Fri, 20 Feb 2026 01:15:29 +0000 Subject: [PATCH 1/7] Restrict PR review triggers to collaborators Co-authored-by: openhands --- .github/workflows/pr-review-by-openhands.yml | 54 +++++++++++++++---- plugins/pr-review/README.md | 18 ++++--- .../workflows/pr-review-by-openhands.yml | 54 +++++++++++++++---- 3 files changed, 96 insertions(+), 30 deletions(-) diff --git a/.github/workflows/pr-review-by-openhands.yml b/.github/workflows/pr-review-by-openhands.yml index 9cf0523..ee56471 100644 --- a/.github/workflows/pr-review-by-openhands.yml +++ b/.github/workflows/pr-review-by-openhands.yml @@ -21,24 +21,56 @@ permissions: jobs: pr-review: - # Run when one of the following conditions is met: - # 1. A new non-draft PR is opened by a non-first-time contributor, OR - # 2. A draft PR is converted to ready for review by a non-first-time contributor, OR - # 3. 'review-this' label is added, OR - # 4. openhands-agent or all-hands-bot is requested as a reviewer - # Note: FIRST_TIME_CONTRIBUTOR and NONE PRs require manual trigger via label/reviewer request. + # Automatic reviews only run for collaborators (author association: COLLABORATOR/MEMBER/OWNER). + # Manual triggers (label/reviewer request) still work for any PR, but only when initiated + # by a user with write (or higher) permission on the repository. if: | - (github.event.action == 'opened' && github.event.pull_request.draft == false && github.event.pull_request.author_association != 'FIRST_TIME_CONTRIBUTOR' && github.event.pull_request.author_association != 'NONE') || - (github.event.action == 'ready_for_review' && github.event.pull_request.author_association != 'FIRST_TIME_CONTRIBUTOR' && github.event.pull_request.author_association != 'NONE') || - github.event.label.name == 'review-this' || - github.event.requested_reviewer.login == 'openhands-agent' || - github.event.requested_reviewer.login == 'all-hands-bot' + (github.event.action == 'opened' && + github.event.pull_request.draft == false && + contains(fromJSON('["COLLABORATOR","MEMBER","OWNER"]'), github.event.pull_request.author_association)) || + (github.event.action == 'ready_for_review' && + contains(fromJSON('["COLLABORATOR","MEMBER","OWNER"]'), github.event.pull_request.author_association)) || + (github.event.action == 'labeled' && github.event.label.name == 'review-this') || + (github.event.action == 'review_requested' && + (github.event.requested_reviewer.login == 'openhands-agent' || github.event.requested_reviewer.login == 'all-hands-bot')) concurrency: group: pr-review-${{ github.event.pull_request.number }} cancel-in-progress: true runs-on: ubuntu-24.04 steps: + - name: Check trigger actor permission (write or higher) + id: trigger-permission + if: github.event.action == 'labeled' || github.event.action == 'review_requested' + uses: actions/github-script@v7 + with: + github-token: ${{ github.token }} + script: | + const allowed = new Set(["admin", "maintain", "write"]); + const username = context.actor; + let permission = "none"; + + try { + const { data } = await github.rest.repos.getCollaboratorPermissionLevel({ + owner: context.repo.owner, + repo: context.repo.repo, + username, + }); + permission = data.permission; + } catch (e) { + // Non-collaborators return 404 + if (e.status !== 404) throw e; + } + + core.setOutput("permission", permission); + core.setOutput("allowed", allowed.has(permission) ? "true" : "false"); + + - name: Skip untrusted trigger + if: (github.event.action == 'labeled' || github.event.action == 'review_requested') && steps.trigger-permission.outputs.allowed != 'true' + run: | + echo "Skipping PR review: actor '${{ github.actor }}' has permission '${{ steps.trigger-permission.outputs.permission }}'" + - name: Run PR Review + if: github.event.action == 'opened' || github.event.action == 'ready_for_review' || steps.trigger-permission.outputs.allowed == 'true' uses: OpenHands/extensions/plugins/pr-review@main with: llm-model: litellm_proxy/claude-sonnet-4-5-20250929 diff --git a/plugins/pr-review/README.md b/plugins/pr-review/README.md index 4c457fb..d45b821 100644 --- a/plugins/pr-review/README.md +++ b/plugins/pr-review/README.md @@ -115,17 +115,19 @@ Create a `review-this` label for manual review triggers: PR reviews are automatically triggered when: -1. A new non-draft PR is opened (by non-first-time contributors) -2. A draft PR is marked as ready for review -3. The `review-this` label is added -4. `openhands-agent` or `all-hands-bot` is requested as a reviewer +1. A new non-draft PR is opened by a collaborator (author association: `COLLABORATOR`, `MEMBER`, or `OWNER`) +2. A draft PR is marked as ready for review by a collaborator +3. The `review-this` label is added by a user with **write (or higher)** permission +4. `openhands-agent` or `all-hands-bot` is requested as a reviewer by a user with **write (or higher)** permission ### Requesting a Review -**Option 1: Request as Reviewer (Recommended)** +Only users with **write (or higher)** permission can trigger a review (to prevent untrusted PR authors from triggering the agent via prompt injection). + +**Option 1: Request as Reviewer** 1. Open the PR 2. Click **Reviewers** in the sidebar -3. Select `openhands-agent` as a reviewer +3. Select `openhands-agent` (or `all-hands-bot`) as a reviewer **Option 2: Add Label** 1. Open the PR @@ -251,7 +253,7 @@ Also update any `sdk-repo` and `sdk-version` inputs to `extensions-repo` and `ex ### Review Not Triggered 1. Check that the workflow file is in `.github/workflows/` -2. Verify the PR author association (first-time contributors need manual trigger) +2. Verify the PR author association (automatic runs only for collaborators; otherwise a maintainer must trigger via label/reviewer request) 3. Ensure secrets are configured correctly ### Review Comments Not Appearing @@ -269,7 +271,7 @@ If you see rate limit errors: ## Security - Uses `pull_request_target` to safely access secrets for fork PRs -- Only triggers for trusted contributors or when maintainers add labels/reviewers +- Automatic reviews only run for collaborators; manual triggers require **write (or higher)** permission - PR code is checked out explicitly; secrets are not exposed to PR code - Credentials are not persisted during checkout diff --git a/plugins/pr-review/workflows/pr-review-by-openhands.yml b/plugins/pr-review/workflows/pr-review-by-openhands.yml index 9cf0523..ee56471 100644 --- a/plugins/pr-review/workflows/pr-review-by-openhands.yml +++ b/plugins/pr-review/workflows/pr-review-by-openhands.yml @@ -21,24 +21,56 @@ permissions: jobs: pr-review: - # Run when one of the following conditions is met: - # 1. A new non-draft PR is opened by a non-first-time contributor, OR - # 2. A draft PR is converted to ready for review by a non-first-time contributor, OR - # 3. 'review-this' label is added, OR - # 4. openhands-agent or all-hands-bot is requested as a reviewer - # Note: FIRST_TIME_CONTRIBUTOR and NONE PRs require manual trigger via label/reviewer request. + # Automatic reviews only run for collaborators (author association: COLLABORATOR/MEMBER/OWNER). + # Manual triggers (label/reviewer request) still work for any PR, but only when initiated + # by a user with write (or higher) permission on the repository. if: | - (github.event.action == 'opened' && github.event.pull_request.draft == false && github.event.pull_request.author_association != 'FIRST_TIME_CONTRIBUTOR' && github.event.pull_request.author_association != 'NONE') || - (github.event.action == 'ready_for_review' && github.event.pull_request.author_association != 'FIRST_TIME_CONTRIBUTOR' && github.event.pull_request.author_association != 'NONE') || - github.event.label.name == 'review-this' || - github.event.requested_reviewer.login == 'openhands-agent' || - github.event.requested_reviewer.login == 'all-hands-bot' + (github.event.action == 'opened' && + github.event.pull_request.draft == false && + contains(fromJSON('["COLLABORATOR","MEMBER","OWNER"]'), github.event.pull_request.author_association)) || + (github.event.action == 'ready_for_review' && + contains(fromJSON('["COLLABORATOR","MEMBER","OWNER"]'), github.event.pull_request.author_association)) || + (github.event.action == 'labeled' && github.event.label.name == 'review-this') || + (github.event.action == 'review_requested' && + (github.event.requested_reviewer.login == 'openhands-agent' || github.event.requested_reviewer.login == 'all-hands-bot')) concurrency: group: pr-review-${{ github.event.pull_request.number }} cancel-in-progress: true runs-on: ubuntu-24.04 steps: + - name: Check trigger actor permission (write or higher) + id: trigger-permission + if: github.event.action == 'labeled' || github.event.action == 'review_requested' + uses: actions/github-script@v7 + with: + github-token: ${{ github.token }} + script: | + const allowed = new Set(["admin", "maintain", "write"]); + const username = context.actor; + let permission = "none"; + + try { + const { data } = await github.rest.repos.getCollaboratorPermissionLevel({ + owner: context.repo.owner, + repo: context.repo.repo, + username, + }); + permission = data.permission; + } catch (e) { + // Non-collaborators return 404 + if (e.status !== 404) throw e; + } + + core.setOutput("permission", permission); + core.setOutput("allowed", allowed.has(permission) ? "true" : "false"); + + - name: Skip untrusted trigger + if: (github.event.action == 'labeled' || github.event.action == 'review_requested') && steps.trigger-permission.outputs.allowed != 'true' + run: | + echo "Skipping PR review: actor '${{ github.actor }}' has permission '${{ steps.trigger-permission.outputs.permission }}'" + - name: Run PR Review + if: github.event.action == 'opened' || github.event.action == 'ready_for_review' || steps.trigger-permission.outputs.allowed == 'true' uses: OpenHands/extensions/plugins/pr-review@main with: llm-model: litellm_proxy/claude-sonnet-4-5-20250929 From e3ccca4cb426017842b7cab2c718c429bb2b4215 Mon Sep 17 00:00:00 2001 From: enyst Date: Fri, 20 Feb 2026 01:28:24 +0000 Subject: [PATCH 2/7] Clarify triggering user permission gate wording Co-authored-by: openhands --- .github/workflows/pr-review-by-openhands.yml | 10 +++++++--- plugins/pr-review/README.md | 4 +++- plugins/pr-review/workflows/pr-review-by-openhands.yml | 10 +++++++--- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/.github/workflows/pr-review-by-openhands.yml b/.github/workflows/pr-review-by-openhands.yml index ee56471..b15cf94 100644 --- a/.github/workflows/pr-review-by-openhands.yml +++ b/.github/workflows/pr-review-by-openhands.yml @@ -22,8 +22,12 @@ permissions: jobs: pr-review: # Automatic reviews only run for collaborators (author association: COLLABORATOR/MEMBER/OWNER). - # Manual triggers (label/reviewer request) still work for any PR, but only when initiated - # by a user with write (or higher) permission on the repository. + # + # Manual triggers (label/reviewer request) still work for any PR, but are permission-gated: + # we only proceed if the *triggering user* has write (or higher) permission. + # + # "Triggering user" here means `github.actor` / `context.actor`: the account that caused + # this workflow run (e.g. the user who added the label or requested the reviewer). if: | (github.event.action == 'opened' && github.event.pull_request.draft == false && @@ -38,7 +42,7 @@ jobs: cancel-in-progress: true runs-on: ubuntu-24.04 steps: - - name: Check trigger actor permission (write or higher) + - name: Verify triggering user permission (write or higher) id: trigger-permission if: github.event.action == 'labeled' || github.event.action == 'review_requested' uses: actions/github-script@v7 diff --git a/plugins/pr-review/README.md b/plugins/pr-review/README.md index d45b821..35df508 100644 --- a/plugins/pr-review/README.md +++ b/plugins/pr-review/README.md @@ -122,7 +122,9 @@ PR reviews are automatically triggered when: ### Requesting a Review -Only users with **write (or higher)** permission can trigger a review (to prevent untrusted PR authors from triggering the agent via prompt injection). +Only users with **write (or higher)** permission can trigger a review. + +In this workflow, the **triggering user** is `github.actor`: the account that performed the action that started the run (e.g. the person who added the `review-this` label or requested the reviewer) — not necessarily the PR author. **Option 1: Request as Reviewer** 1. Open the PR diff --git a/plugins/pr-review/workflows/pr-review-by-openhands.yml b/plugins/pr-review/workflows/pr-review-by-openhands.yml index ee56471..b15cf94 100644 --- a/plugins/pr-review/workflows/pr-review-by-openhands.yml +++ b/plugins/pr-review/workflows/pr-review-by-openhands.yml @@ -22,8 +22,12 @@ permissions: jobs: pr-review: # Automatic reviews only run for collaborators (author association: COLLABORATOR/MEMBER/OWNER). - # Manual triggers (label/reviewer request) still work for any PR, but only when initiated - # by a user with write (or higher) permission on the repository. + # + # Manual triggers (label/reviewer request) still work for any PR, but are permission-gated: + # we only proceed if the *triggering user* has write (or higher) permission. + # + # "Triggering user" here means `github.actor` / `context.actor`: the account that caused + # this workflow run (e.g. the user who added the label or requested the reviewer). if: | (github.event.action == 'opened' && github.event.pull_request.draft == false && @@ -38,7 +42,7 @@ jobs: cancel-in-progress: true runs-on: ubuntu-24.04 steps: - - name: Check trigger actor permission (write or higher) + - name: Verify triggering user permission (write or higher) id: trigger-permission if: github.event.action == 'labeled' || github.event.action == 'review_requested' uses: actions/github-script@v7 From dfb5679c0b74bbc445ee5cbf52eb030bf2f0acc5 Mon Sep 17 00:00:00 2001 From: enyst Date: Fri, 20 Feb 2026 01:40:49 +0000 Subject: [PATCH 3/7] Remove skip step from PR review workflows Co-authored-by: openhands --- .github/workflows/pr-review-by-openhands.yml | 7 ++----- plugins/pr-review/workflows/pr-review-by-openhands.yml | 7 ++----- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/.github/workflows/pr-review-by-openhands.yml b/.github/workflows/pr-review-by-openhands.yml index b15cf94..f6c0306 100644 --- a/.github/workflows/pr-review-by-openhands.yml +++ b/.github/workflows/pr-review-by-openhands.yml @@ -68,12 +68,9 @@ jobs: core.setOutput("permission", permission); core.setOutput("allowed", allowed.has(permission) ? "true" : "false"); - - name: Skip untrusted trigger - if: (github.event.action == 'labeled' || github.event.action == 'review_requested') && steps.trigger-permission.outputs.allowed != 'true' - run: | - echo "Skipping PR review: actor '${{ github.actor }}' has permission '${{ steps.trigger-permission.outputs.permission }}'" - - name: Run PR Review + # Automatic trusted PRs (opened/ready_for_review by collaborators) + # OR manual triggers (label/review_requested) when the triggering user has write+ permission. if: github.event.action == 'opened' || github.event.action == 'ready_for_review' || steps.trigger-permission.outputs.allowed == 'true' uses: OpenHands/extensions/plugins/pr-review@main with: diff --git a/plugins/pr-review/workflows/pr-review-by-openhands.yml b/plugins/pr-review/workflows/pr-review-by-openhands.yml index b15cf94..f6c0306 100644 --- a/plugins/pr-review/workflows/pr-review-by-openhands.yml +++ b/plugins/pr-review/workflows/pr-review-by-openhands.yml @@ -68,12 +68,9 @@ jobs: core.setOutput("permission", permission); core.setOutput("allowed", allowed.has(permission) ? "true" : "false"); - - name: Skip untrusted trigger - if: (github.event.action == 'labeled' || github.event.action == 'review_requested') && steps.trigger-permission.outputs.allowed != 'true' - run: | - echo "Skipping PR review: actor '${{ github.actor }}' has permission '${{ steps.trigger-permission.outputs.permission }}'" - - name: Run PR Review + # Automatic trusted PRs (opened/ready_for_review by collaborators) + # OR manual triggers (label/review_requested) when the triggering user has write+ permission. if: github.event.action == 'opened' || github.event.action == 'ready_for_review' || steps.trigger-permission.outputs.allowed == 'true' uses: OpenHands/extensions/plugins/pr-review@main with: From ef0d56426b5aa5e51e3d568fad9537044c02cf99 Mon Sep 17 00:00:00 2001 From: Engel Nyst Date: Fri, 20 Feb 2026 03:44:33 +0100 Subject: [PATCH 4/7] Apply suggestion from @enyst --- plugins/pr-review/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/pr-review/README.md b/plugins/pr-review/README.md index 35df508..de1e4c6 100644 --- a/plugins/pr-review/README.md +++ b/plugins/pr-review/README.md @@ -129,7 +129,7 @@ In this workflow, the **triggering user** is `github.actor`: the account that pe **Option 1: Request as Reviewer** 1. Open the PR 2. Click **Reviewers** in the sidebar -3. Select `openhands-agent` (or `all-hands-bot`) as a reviewer +3. Select `openhands-agent` as a reviewer **Option 2: Add Label** 1. Open the PR From 27a9ec82d241874a489e729ee04cb8054c261920 Mon Sep 17 00:00:00 2001 From: Engel Nyst Date: Fri, 20 Feb 2026 03:59:45 +0100 Subject: [PATCH 5/7] Apply suggestion from @enyst --- plugins/pr-review/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/pr-review/README.md b/plugins/pr-review/README.md index de1e4c6..ce78dfe 100644 --- a/plugins/pr-review/README.md +++ b/plugins/pr-review/README.md @@ -118,7 +118,7 @@ PR reviews are automatically triggered when: 1. A new non-draft PR is opened by a collaborator (author association: `COLLABORATOR`, `MEMBER`, or `OWNER`) 2. A draft PR is marked as ready for review by a collaborator 3. The `review-this` label is added by a user with **write (or higher)** permission -4. `openhands-agent` or `all-hands-bot` is requested as a reviewer by a user with **write (or higher)** permission +4. `openhands-agent` is requested as a reviewer by a user with **write** permission ### Requesting a Review From 27fa4ab402194027665417fd62ddb4dd369dfbc0 Mon Sep 17 00:00:00 2001 From: enyst Date: Sat, 21 Feb 2026 06:15:22 +0000 Subject: [PATCH 6/7] Align PR review workflow conditions with agent-sdk Co-authored-by: openhands --- .github/workflows/pr-review-by-openhands.yml | 74 ++++++-------------- 1 file changed, 20 insertions(+), 54 deletions(-) diff --git a/.github/workflows/pr-review-by-openhands.yml b/.github/workflows/pr-review-by-openhands.yml index f6c0306..7080764 100644 --- a/.github/workflows/pr-review-by-openhands.yml +++ b/.github/workflows/pr-review-by-openhands.yml @@ -2,16 +2,11 @@ name: PR Review by OpenHands on: - # Use pull_request_target to allow fork PRs to access secrets when triggered by maintainers - # Security: This workflow runs when: - # 1. A new PR is opened (non-draft), OR - # 2. A draft PR is marked as ready for review, OR - # 3. A maintainer adds the 'review-this' label, OR - # 4. A maintainer requests openhands-agent or all-hands-bot as a reviewer - # Adding labels and requesting reviewers requires write access. - # The PR code is explicitly checked out for review, but secrets are only accessible - # because the workflow runs in the base repository context. - pull_request_target: + # TEMPORARY MITIGATION (Clinejection hardening) + # + # We temporarily avoid `pull_request_target` here. We'll restore it after the PR review + # workflow is fully hardened for untrusted execution. + pull_request: types: [opened, ready_for_review, labeled, review_requested] permissions: @@ -21,57 +16,28 @@ permissions: jobs: pr-review: - # Automatic reviews only run for collaborators (author association: COLLABORATOR/MEMBER/OWNER). - # - # Manual triggers (label/reviewer request) still work for any PR, but are permission-gated: - # we only proceed if the *triggering user* has write (or higher) permission. - # - # "Triggering user" here means `github.actor` / `context.actor`: the account that caused - # this workflow run (e.g. the user who added the label or requested the reviewer). + # Note: fork PRs will not have access to repository secrets under `pull_request`. + # Skip forks to avoid noisy failures until we restore a hardened `pull_request_target` flow. if: | - (github.event.action == 'opened' && - github.event.pull_request.draft == false && - contains(fromJSON('["COLLABORATOR","MEMBER","OWNER"]'), github.event.pull_request.author_association)) || - (github.event.action == 'ready_for_review' && - contains(fromJSON('["COLLABORATOR","MEMBER","OWNER"]'), github.event.pull_request.author_association)) || - (github.event.action == 'labeled' && github.event.label.name == 'review-this') || - (github.event.action == 'review_requested' && - (github.event.requested_reviewer.login == 'openhands-agent' || github.event.requested_reviewer.login == 'all-hands-bot')) + github.event.pull_request.head.repo.full_name == github.repository && + ( + (github.event.action == 'opened' && github.event.pull_request.draft == false) || + github.event.action == 'ready_for_review' || + (github.event.action == 'labeled' && github.event.label.name == 'review-this') || + ( + github.event.action == 'review_requested' && + ( + github.event.requested_reviewer.login == 'openhands-agent' || + github.event.requested_reviewer.login == 'all-hands-bot' + ) + ) + ) concurrency: group: pr-review-${{ github.event.pull_request.number }} cancel-in-progress: true runs-on: ubuntu-24.04 steps: - - name: Verify triggering user permission (write or higher) - id: trigger-permission - if: github.event.action == 'labeled' || github.event.action == 'review_requested' - uses: actions/github-script@v7 - with: - github-token: ${{ github.token }} - script: | - const allowed = new Set(["admin", "maintain", "write"]); - const username = context.actor; - let permission = "none"; - - try { - const { data } = await github.rest.repos.getCollaboratorPermissionLevel({ - owner: context.repo.owner, - repo: context.repo.repo, - username, - }); - permission = data.permission; - } catch (e) { - // Non-collaborators return 404 - if (e.status !== 404) throw e; - } - - core.setOutput("permission", permission); - core.setOutput("allowed", allowed.has(permission) ? "true" : "false"); - - name: Run PR Review - # Automatic trusted PRs (opened/ready_for_review by collaborators) - # OR manual triggers (label/review_requested) when the triggering user has write+ permission. - if: github.event.action == 'opened' || github.event.action == 'ready_for_review' || steps.trigger-permission.outputs.allowed == 'true' uses: OpenHands/extensions/plugins/pr-review@main with: llm-model: litellm_proxy/claude-sonnet-4-5-20250929 From 1d5bb59d237de3ffb915ab41d9f7e79ed0d967f9 Mon Sep 17 00:00:00 2001 From: openhands Date: Sat, 21 Feb 2026 07:11:35 +0000 Subject: [PATCH 7/7] Sync pr-review workflow copy with canonical Co-authored-by: openhands --- .../workflows/pr-review-by-openhands.yml | 74 +++++-------------- 1 file changed, 20 insertions(+), 54 deletions(-) diff --git a/plugins/pr-review/workflows/pr-review-by-openhands.yml b/plugins/pr-review/workflows/pr-review-by-openhands.yml index f6c0306..7080764 100644 --- a/plugins/pr-review/workflows/pr-review-by-openhands.yml +++ b/plugins/pr-review/workflows/pr-review-by-openhands.yml @@ -2,16 +2,11 @@ name: PR Review by OpenHands on: - # Use pull_request_target to allow fork PRs to access secrets when triggered by maintainers - # Security: This workflow runs when: - # 1. A new PR is opened (non-draft), OR - # 2. A draft PR is marked as ready for review, OR - # 3. A maintainer adds the 'review-this' label, OR - # 4. A maintainer requests openhands-agent or all-hands-bot as a reviewer - # Adding labels and requesting reviewers requires write access. - # The PR code is explicitly checked out for review, but secrets are only accessible - # because the workflow runs in the base repository context. - pull_request_target: + # TEMPORARY MITIGATION (Clinejection hardening) + # + # We temporarily avoid `pull_request_target` here. We'll restore it after the PR review + # workflow is fully hardened for untrusted execution. + pull_request: types: [opened, ready_for_review, labeled, review_requested] permissions: @@ -21,57 +16,28 @@ permissions: jobs: pr-review: - # Automatic reviews only run for collaborators (author association: COLLABORATOR/MEMBER/OWNER). - # - # Manual triggers (label/reviewer request) still work for any PR, but are permission-gated: - # we only proceed if the *triggering user* has write (or higher) permission. - # - # "Triggering user" here means `github.actor` / `context.actor`: the account that caused - # this workflow run (e.g. the user who added the label or requested the reviewer). + # Note: fork PRs will not have access to repository secrets under `pull_request`. + # Skip forks to avoid noisy failures until we restore a hardened `pull_request_target` flow. if: | - (github.event.action == 'opened' && - github.event.pull_request.draft == false && - contains(fromJSON('["COLLABORATOR","MEMBER","OWNER"]'), github.event.pull_request.author_association)) || - (github.event.action == 'ready_for_review' && - contains(fromJSON('["COLLABORATOR","MEMBER","OWNER"]'), github.event.pull_request.author_association)) || - (github.event.action == 'labeled' && github.event.label.name == 'review-this') || - (github.event.action == 'review_requested' && - (github.event.requested_reviewer.login == 'openhands-agent' || github.event.requested_reviewer.login == 'all-hands-bot')) + github.event.pull_request.head.repo.full_name == github.repository && + ( + (github.event.action == 'opened' && github.event.pull_request.draft == false) || + github.event.action == 'ready_for_review' || + (github.event.action == 'labeled' && github.event.label.name == 'review-this') || + ( + github.event.action == 'review_requested' && + ( + github.event.requested_reviewer.login == 'openhands-agent' || + github.event.requested_reviewer.login == 'all-hands-bot' + ) + ) + ) concurrency: group: pr-review-${{ github.event.pull_request.number }} cancel-in-progress: true runs-on: ubuntu-24.04 steps: - - name: Verify triggering user permission (write or higher) - id: trigger-permission - if: github.event.action == 'labeled' || github.event.action == 'review_requested' - uses: actions/github-script@v7 - with: - github-token: ${{ github.token }} - script: | - const allowed = new Set(["admin", "maintain", "write"]); - const username = context.actor; - let permission = "none"; - - try { - const { data } = await github.rest.repos.getCollaboratorPermissionLevel({ - owner: context.repo.owner, - repo: context.repo.repo, - username, - }); - permission = data.permission; - } catch (e) { - // Non-collaborators return 404 - if (e.status !== 404) throw e; - } - - core.setOutput("permission", permission); - core.setOutput("allowed", allowed.has(permission) ? "true" : "false"); - - name: Run PR Review - # Automatic trusted PRs (opened/ready_for_review by collaborators) - # OR manual triggers (label/review_requested) when the triggering user has write+ permission. - if: github.event.action == 'opened' || github.event.action == 'ready_for_review' || steps.trigger-permission.outputs.allowed == 'true' uses: OpenHands/extensions/plugins/pr-review@main with: llm-model: litellm_proxy/claude-sonnet-4-5-20250929