diff --git a/.github/scripts/review-checklist.js b/.github/scripts/review-checklist.js index ed9adfb9c37..9ef02a7242a 100644 --- a/.github/scripts/review-checklist.js +++ b/.github/scripts/review-checklist.js @@ -186,7 +186,27 @@ function buildBody(touchedAreas, approvedUsers, confirmedRequested) { module.exports = async function run({ github, context, core }) { const { owner, repo } = context.repo; - const pr_number = context.payload.pull_request.number; + let pr_number; + + if (context.eventName === 'workflow_run') { + // workflow_run.pull_requests is empty for fork PRs — look up by head SHA instead. + const headSha = context.payload.workflow_run.head_sha; + const openPRs = await github.paginate(github.rest.pulls.list, { + owner, repo, state: 'open', per_page: 100, + }); + const pr = openPRs.find(p => p.head.sha === headSha); + if (!pr) { + core.info('No open PR found matching this workflow_run — skipping'); + return; + } + if (pr.base.ref !== 'develop') { + core.info(`PR #${pr.number} targets ${pr.base.ref}, not develop — skipping`); + return; + } + pr_number = pr.number; + } else { + pr_number = context.payload.pull_request.number; + } // ---------------------------------------------------------------- // Configuration @@ -202,10 +222,6 @@ module.exports = async function run({ github, context, core }) { // Covers: hdf5.h (umbrella), H5*public.h / H5*develop.h (per-module), // VFD driver headers included by hdf5.h, and VOL connector headers. // - // NOTE: Fork PRs (head.repo != base.repo) are intentionally excluded. - // They run with a read-only token and cannot post comments or request - // reviewers. Fork coverage would require a pull_request_target job. - // // NOTE: Team owners (@org/team) in CODEOWNERS are not supported. // Only individual GitHub logins are handled. If teams are added, // extend parsing and reviewer requests to use team_reviewers. @@ -349,8 +365,8 @@ module.exports = async function run({ github, context, core }) { // ---------------------------------------------------------------- // 6. Auto-assign reviewers (pull_request events only, not reviews). // ---------------------------------------------------------------- - if (context.eventName !== 'pull_request_review') { - const prAuthor = context.payload.pull_request.user.login; + if (context.eventName !== 'pull_request_review' && context.eventName !== 'workflow_run') { + const prAuthor = prData.user.login; // Assign the PR author only if they are a code owner. if (allCodeOwners.has(prAuthor)) { diff --git a/.github/workflows/review-checklist-gather.yml b/.github/workflows/review-checklist-gather.yml new file mode 100644 index 00000000000..8da12c506f7 --- /dev/null +++ b/.github/workflows/review-checklist-gather.yml @@ -0,0 +1,26 @@ +name: Review Checklist (gather) + +# Minimal signal workflow: fires on pull_request_review so that the +# privileged review-checklist.yml (triggered via workflow_run) can +# update the checklist with the latest approval state. +# +# This workflow intentionally does nothing except complete — its only +# purpose is to unblock the workflow_run trigger in review-checklist.yml. +# +# Requires the repo "Fork pull request workflows from outside collaborators" +# setting to be "Require approval for first-time contributors" (not all +# outside collaborators) so regular fork contributors' reviews fire this +# immediately without a maintainer approval gate. + +on: + pull_request_review: + types: [submitted] + +permissions: {} + +jobs: + signal: + runs-on: ubuntu-latest + if: github.event.pull_request.base.ref == 'develop' + steps: + - run: echo "Review submitted on PR ${{ github.event.pull_request.number }}" diff --git a/.github/workflows/review-checklist.yml b/.github/workflows/review-checklist.yml index 097f8702762..ca5565e94f5 100644 --- a/.github/workflows/review-checklist.yml +++ b/.github/workflows/review-checklist.yml @@ -5,16 +5,32 @@ name: Review Checklist # # Reviewer lists are derived entirely from .github/CODEOWNERS — no duplication. # To add an area or change owners, edit only CODEOWNERS. +# +# Uses pull_request_target so the workflow runs with the base repo's full token +# even for fork PRs. The checkout and script execution always use the base +# branch (develop), never the fork's code — this is the safe posture for +# pull_request_target. +# +# Approval boxes are also updated immediately when a review is submitted, via +# a two-workflow pattern: review-checklist-gather.yml fires on pull_request_review +# (read-only token) and this workflow fires on its completion via workflow_run +# (full write token, no fork approval gate). Requires the repo setting +# "Fork pull request workflows from outside collaborators" to be +# "Require approval for first-time contributors". on: - pull_request: + # zizmor: ignore[pull-request-target] + # Safe: checkout has no ref: override so the base branch (develop) is always + # used — the fork's code is never checked out or executed. + pull_request_target: types: [opened, synchronize, reopened] branches: [develop] - pull_request_review: - types: [submitted] + workflow_run: + workflows: ["Review Checklist (gather)"] + types: [completed] concurrency: - group: review-checklist-${{ github.event.pull_request.number }} + group: review-checklist-${{ github.event.pull_request.number || github.event.workflow_run.head_sha }} cancel-in-progress: true permissions: @@ -25,18 +41,14 @@ permissions: jobs: checklist: runs-on: ubuntu-latest - # Only run on PRs targeting develop from within the same repo (not forks). - # For review events, only run on approvals targeting develop. - if: | - github.event.pull_request.base.ref == 'develop' && - github.event.pull_request.head.repo.full_name == github.repository && - (github.event_name == 'pull_request' || - (github.event_name == 'pull_request_review' && - github.event.review.state == 'approved')) - + if: > + (github.event_name == 'pull_request_target' && github.event.pull_request.base.ref == 'develop') || + (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success') steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + with: + persist-credentials: false + - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: script: | const run = require('./.github/scripts/review-checklist.js'); diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml new file mode 100644 index 00000000000..0aa86848235 --- /dev/null +++ b/.github/workflows/zizmor.yml @@ -0,0 +1,38 @@ +name: zizmor + +# Static security analysis for GitHub Actions workflows. +# Findings appear as inline annotations on PRs and in the Security tab. +# Runs zizmor directly via pip to avoid a third-party action dependency. + +on: + push: + branches: [develop] + paths: ['.github/**'] + pull_request: + branches: [develop] + paths: ['.github/**'] + +permissions: {} + +jobs: + zizmor: + runs-on: ubuntu-latest + permissions: + contents: read + security-events: write + steps: + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + with: + persist-credentials: false + - run: | + echo 'zizmor==1.25.2 --hash=sha256:c4246f1344d8dbeffc044d7bb11b131773a7db7eb57d9073c45942dfd3543a1f' > /tmp/zizmor-req.txt + pip install --require-hashes -r /tmp/zizmor-req.txt + - run: zizmor --format sarif . > zizmor-results.sarif + continue-on-error: true + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2 + if: always() + with: + sarif_file: zizmor-results.sarif + category: zizmor