Fix security vulnerability in workflows (#1804)

* Fix a security hole in #1787 found by Arun Murugesan:

"The workflow .github/workflows/eslint-check.yml contained a critical "pwn request" vulnerability that allows any GitHub user to execute arbitrary code with access to repository secrets by opening a pull request."

See https://github.com/preactjs/compressed-size-action/issues/54 for why that action shouldn't be used with pull_request_target

This change in this PR drops compressed-size-action in favour of executing the steps ourselves in two workflows, one which produces the size artifact, and the other which reads the artifact has the permissions to write the message back to the original PR (which is in a third party repo)

* The annotate action also needed pull-requests: write permission (fixes failing run 'ESLint Annotation')

* ci(bundle-size): extract bundle size scripts and simplify workflow

- Add `.github/scripts/measure-bundle-sizes.js` and
  `render-bundle-size-comment.js` to replace inline node scripts
  embedded in workflow YAML, improving readability and reusability
- Refactor `eslint-check.yml` to use the new script files and fix
  checkout steps to handle both PR and non-PR triggers correctly
- Refactor `pr-checks-privileged.yml` to replace the large
  `github-script` block with `render-bundle-size-comment.js` and
  the `marocchino/sticky-pull-request-comment` action; remove the
  now-unnecessary `pr_number.txt` artifact by reading the PR number
  directly from the workflow_run event
- Pin `ataylorme/eslint-annotate-action` to a specific commit SHA
- Add `actions: read` permission where needed for artifact downloads

* ci: add fork PR support and harden workflow

- Look up PR number via API when workflow_run.pull_requests is empty
  (GitHub leaves it empty for fork PRs), falling back gracefully
- Use head SHA instead of branch name for PR checkout to avoid TOCTOU
- Fix formatSignedSize to produce +0 instead of -0 for zero values
- Gate comment steps on successful PR number lookup

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Eoghan Murray <eoghan@getthere.ie>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Justin Halsall
2026-04-01 12:00:00 +08:00
committed by GitHub
parent aab4c553cb
commit 62d2f8504c
4 changed files with 344 additions and 36 deletions

View File

@@ -0,0 +1,85 @@
name: PR Checks (privileged)
# Runs in the base-repo context (privileged) after eslint-check.yml completes.
# Downloads pre-built artifacts and posts PR comments/annotations.
# Never checks out or executes fork code.
on:
workflow_run:
workflows: ['ESLint Check']
types: [completed]
jobs:
comment:
if: >
github.event.workflow_run.event == 'pull_request' &&
github.event.workflow_run.conclusion == 'success'
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
pull-requests: write
steps:
- name: Checkout trusted workflow helpers
uses: actions/checkout@v4
with:
ref: ${{ github.event.repository.default_branch }}
- uses: actions/download-artifact@v4
with:
name: bundle-size-data
github-token: ${{ secrets.GITHUB_TOKEN }}
run-id: ${{ github.event.workflow_run.id }}
- name: Find PR number
id: find-pr
uses: actions/github-script@v7
with:
script: |
const run = context.payload.workflow_run;
if (run.pull_requests && run.pull_requests.length > 0) {
return run.pull_requests[0].number;
}
// Fallback for fork PRs (pull_requests is empty for forks)
const { data: prs } = await github.rest.pulls.list({
owner: context.repo.owner,
repo: context.repo.repo,
head: `${run.head_repository.full_name}:${run.head_branch}`,
state: 'open',
});
if (prs.length === 0) {
core.setFailed('Could not determine PR number');
return;
}
return prs[0].number;
result-encoding: string
- name: Render bundle size comment
if: steps.find-pr.outputs.result
run: |
node .github/scripts/render-bundle-size-comment.js pr-sizes.json base-sizes.json > bundle-size-comment.md
- name: Post bundle size comment
if: steps.find-pr.outputs.result
uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405
with:
header: bundle-size
path: bundle-size-comment.md
number: ${{ steps.find-pr.outputs.result }}
annotate:
if: >
github.event.workflow_run.event == 'pull_request' &&
github.event.workflow_run.conclusion == 'success'
runs-on: ubuntu-latest
permissions:
actions: read
checks: write
steps:
- uses: actions/download-artifact@v4
with:
name: eslint_report.json
github-token: ${{ secrets.GITHUB_TOKEN }}
run-id: ${{ github.event.workflow_run.id }}
- name: Annotate Code Linting Results
uses: ataylorme/eslint-annotate-action@5f4dc2e3af8d3c21b727edb597e5503510b1dc9c
with:
repo-token: '${{ secrets.GITHUB_TOKEN }}'
report-json: 'eslint_report.json'