name: Slash Command Handler on: issue_comment: types: [created, edited] permissions: contents: read pull-requests: write # Required to add labels and reactions actions: write # Required to rerun workflows issues: write # Required for comment reactions in some contexts jobs: slash_command: # Only run if it is a PR and the comment contains a recognized command # Use contains() since startsWith() can't handle leading whitespace/newlines if: > github.event.issue.pull_request && (contains(github.event.comment.body, '/tag-run-ci-label') || contains(github.event.comment.body, '/rerun-failed-ci') || contains(github.event.comment.body, '/tag-and-rerun-ci') || contains(github.event.comment.body, '/rerun-stage') || contains(github.event.comment.body, '/rerun-test')) runs-on: ubuntu-latest steps: # SECURITY: This workflow runs on issue_comment trigger with elevated permissions # (pull-requests: write, actions: write). For non-fork PRs, we can safely checkout # the PR branch to allow testing changes to this handler. For fork PRs, we MUST # stay on main to prevent untrusted code execution with these elevated permissions. - name: Get PR details id: pr shell: bash env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | PR_DATA=$(gh pr view ${{ github.event.issue.number }} --repo ${{ github.repository }} --json headRefName,headRepositoryOwner) || { echo "::error::Failed to fetch PR data" exit 1 } # Use 'empty' filter to handle null/missing values (e.g., deleted forks) HEAD_OWNER=$(echo "$PR_DATA" | jq -r '.headRepositoryOwner.login // empty') REPO_OWNER="${{ github.repository_owner }}" # Treat missing/null owner as fork for security (fail-safe) if [[ -z "$HEAD_OWNER" || "$HEAD_OWNER" != "$REPO_OWNER" ]]; then IS_FORK="true" else IS_FORK="false" fi echo "is_fork=$IS_FORK" >> $GITHUB_OUTPUT echo "ref=$(echo "$PR_DATA" | jq -r '.headRefName')" >> $GITHUB_OUTPUT echo "pr_ref=refs/pull/${{ github.event.issue.number }}/head" >> $GITHUB_OUTPUT echo "PR owner: $HEAD_OWNER, Repo owner: $REPO_OWNER, Is fork: $IS_FORK" - name: Check commenter permission for fork PRs id: perm if: steps.pr.outputs.is_fork == 'true' shell: bash env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | PERM=$(gh api repos/${{ github.repository }}/collaborators/${{ github.event.comment.user.login }}/permission --jq '.permission') || { PERM="none" echo "::warning::Failed to check commenter permission, defaulting to none" } if [[ "$PERM" == "admin" || "$PERM" == "maintain" || "$PERM" == "write" ]]; then echo "safe_to_checkout_pr=true" >> $GITHUB_OUTPUT else echo "safe_to_checkout_pr=false" >> $GITHUB_OUTPUT fi echo "Commenter ${{ github.event.comment.user.login }} permission: $PERM" - name: Checkout code uses: actions/checkout@v4 with: # For non-fork PRs: checkout PR branch by name # For fork PRs with trusted commenter: checkout via refs/pull/N/head # For fork PRs with untrusted commenter: stay on main for security ref: ${{ steps.pr.outputs.is_fork == 'false' && steps.pr.outputs.ref || (steps.perm.outputs.safe_to_checkout_pr == 'true' && steps.pr.outputs.pr_ref || '') }} - name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.10' - name: Install dependencies run: | pip install PyGithub - name: Handle Slash Command env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} REPO_FULL_NAME: ${{ github.repository }} PR_NUMBER: ${{ github.event.issue.number }} COMMENT_ID: ${{ github.event.comment.id }} COMMENT_BODY: ${{ github.event.comment.body }} USER_LOGIN: ${{ github.event.comment.user.login }} run: | python scripts/ci/utils/slash_command_handler.py