fix: Make bundle size workflow work on fork PRs#4619
Open
TrevorBurnham wants to merge 2 commits into
Open
Conversation
The bundle size workflow failed on every fork PR. GitHub downgrades the GITHUB_TOKEN to read-only for pull_request events from forks, so the statuses:write permission the workflow requested was denied, and posting the commit status failed with "Resource not accessible by integration". Split into two workflows with privilege separation: - "Measure bundle size" runs the untrusted PR build with contents:read only and uploads the measured sizes as an artifact. - "Report bundle size" runs on workflow_run (in the base repo context, so it keeps statuses:write even for fork PRs) and posts the commit status. It never checks out or executes PR code. The commit SHA the status is attached to is taken from the trusted workflow_run.head_sha event field, not from the artifact, so untrusted fork code cannot redirect the status onto an arbitrary commit. head_sha is the PR head (not the merge commit the workflow previously used).
961a893 to
2b935c8
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #4619 +/- ##
==========================================
+ Coverage 96.45% 97.51% +1.06%
==========================================
Files 801 948 +147
Lines 22872 30334 +7462
Branches 7490 11059 +3569
==========================================
+ Hits 22061 29580 +7519
+ Misses 804 707 -97
- Partials 7 47 +40 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
amanabiy
reviewed
Jul 10, 2026
| runs-on: ubuntu-latest | ||
| if: > | ||
| github.event.workflow_run.event == 'pull_request' || | ||
| github.event.workflow_run.event == 'push' |
Member
There was a problem hiding this comment.
We don't push to main. Why do we need the github.event.workflow_run.event == 'push' Can we remove it?
Contributor
Author
There was a problem hiding this comment.
Good call. You're right that it's unnecessary — I've removed it in 2e6fd07.
Two notes on the reasoning:
- Technically
maindoes getpushevents (every merged PR fires one), and the measure workflow was triggering on them. But on apushthere's nopull_request.base.sha, so the base and PR checkouts resolved to the same commit and the report was always a zero-delta "no change" — while still paying for a fullnpm i+ build. - So rather than just drop the
pusharm here, I dropped thepush:trigger frombundle-size.ymltoo. Otherwise every merge to main would still run the measurement and upload an artifact that nothing reports on.
Reporting is now PR-only.
The push path measured base==head (no PR base SHA), so it only ever produced a zero-delta status on main commits while still running the full npm i + build. Remove the push trigger from the measure workflow and the corresponding arm from the report workflow's gate.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR fixes the Measure bundle size workflow, which fails on every pull request opened from a fork (example run).
For
pull_requestevents triggered from a fork, GitHub forcibly downgrades theGITHUB_TOKENto read-only regardless of thepermissions:the workflow requests — a security measure that prevents untrusted fork code from obtaining a write-capable token. The workflow requestedstatuses: writeand calledrepos.createCommitStatus(...)to post the bundle-size result, so on fork PRs the call failed with:Because contributors routinely open PRs from forks, the workflow showed a red ❌ on those PRs and never reported a size delta.
Fix: privilege separation across two workflows
The fix here is to split the work in two:
bundle-size.yml(Measure bundle size) — runs the untrusted PR build (npm i,quick-build, esbuild bundle). It now requests onlycontents: read, does no API writes, and uploads the measurement (output.json,output-basebranch.json,metadata.json) as thesize-reportartifact.bundle-size-report.yml(Report bundle size) — new workflow triggered viaworkflow_runwhen the measurement completes. Becauseworkflow_runexecutes in the base repository's context, itsGITHUB_TOKENkeepsstatuses: writeeven for fork PRs. It downloads the artifact and posts the commit status.status-report.jsgains a singlereportBundleSize()entry point used by the report workflow; the per-state helpers it replaced were removed.Behavioural notes
metadata.jsonis uploaded (written before the build, uploaded withif: always()), which is enough for the report workflow to post anerrorstatus — preserving the previous failure-reporting behaviour.pendingstatus was dropped; the in-progress state is already visible from the Actions check itself.workflow_runworkflows always run from the definition on the default branch, so the report half can only be fully exercised once this lands onmain. Recommend a quick validation on a follow-upmaincommit after merge.Security note: the status target is taken from a trusted source
The report workflow runs privileged, so anything it consumes from the measurement job must be treated as attacker-controlled — that job executes untrusted PR code (
npm ipostinstall scripts, then the build) and can write arbitrary content into the artifact.So the SHA is read from the trusted
github.event.workflow_run.head_shaevent field instead of any artifact. This is GitHub-generated, not attacker-writable, and resolves to the PR head commit — which is also strictly more correct than the workflow's previouscontext.sha, an ephemeral merge commit. With the SHA sourced from the event, no metadata round-trip through the artifact is needed; the artifact now carries only the numeric measurements, whose values flow solely into number formatting.Additional hardening:
npm i/build.${{ }}expression is interpolated into anyrun/scriptbody — dynamic values (head SHA, conclusion, target URL) are passed viaenv:and read throughprocess.env, closing the expression-injection hole.github.event.workflow_run.id.By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.