fix(workflow): modified PR Percy workflow for better security#673
Conversation
|
There was a problem hiding this comment.
Pull request overview
This PR hardens Percy visual regression for forked pull requests by replacing the insecure pull_request_target pattern with a two-stage workflow: an unprivileged PR build that produces a static Storybook artifact, followed by a privileged workflow_run job that runs Percy using secrets without executing fork code.
Changes:
- Add a new
percy-build.ymlworkflow to build Storybook onpull_requestand upload it (plus metadata) as a short-lived artifact. - Update
percy.ymlto run PR Percy snapshots onworkflow_runsuccess, download the artifact, and post commit statuses. - Keep the
push-to-mainbaseline Percy job inpercy.yml.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| .github/workflows/percy.yml | Switch PR Percy execution to workflow_run, download Stage 1 artifact, run Percy from trusted code, and report commit status. |
| .github/workflows/percy-build.yml | New Stage 1 PR workflow that builds static Storybook and uploads it as an artifact with component/PR metadata. |
Comments suppressed due to low confidence (1)
.github/workflows/percy.yml:20
- The workflow grants
pull-requests: writeandchecks: write, but this job only uses the Statuses API (repos.createCommitStatus) and artifact download. Reducing permissions to the minimum needed (e.g.,contents: read,statuses: write,actions: read) lowers risk, especially since this workflow is explicitly security-motivated.
permissions:
contents: read
pull-requests: write
checks: write
statuses: write
|
Note: the dual Percy run is an unavoidable artifact of switching the Percy workflow from the build-layer run to the workflow-layer run. |
Summary
Replaces the
pull_request_targetapproach with a secure two-stage workflow that enables Percy visual regression snapshots for fork PRs without exposing secrets to untrusted code.Background
Percy snapshots were silently failing for all external contributor PRs because GitHub withholds repository secrets from
pull_requestworkflows triggered by forks —PERCY_TOKENwas always an empty string for fork PRs.A first fix using
pull_request_targetwas implemented, but due to a recent exploit of this pattern: fork code running in a privileged context poisoned the npm cache, which was later restored by a release workflow withid-token: write, enabling unauthorized npm publishes. Given that ourci.ymlrelease job also hasid-token: write, the risk profile was unacceptable.Solution
Split Percy CI into two isolated workflow stages:
Stage 1 —
percy-build.yml(triggered bypull_request, no secrets)pull_requestevent — no elevated privilegesdetect-changed-componentsactionStage 2 —
percy.yml(triggered byworkflow_run, has secrets)pendingcommit status to the PR immediately so the check is visible while Percy runsworkflow_runevent payload for reliabilitypercy storybookagainst the static artifact, filtered to changed components when applicablesuccessorfailurecommit status back to the PR regardless of outcome (if: always())PERCY_TOKENonly ever exists in this stage, in a fully trusted contextThe main branch baseline job is unchanged — it continues to run
snapshots:allon push tomain.Security Properties
PERCY_TOKENis fully isolated to Stage 2, which runs entirely from base-branch codeTrade-offs
--include— slightly slower than the previous partial-build approach but required for the isolation model