ci: auto-rebuild dist/ for Dependabot production bumps#444
Conversation
Add a two-workflow pipeline that rebuilds the transpiled dist/ whenever Dependabot bumps a production dependency, so consumers of the action get the updated JavaScript without a manual rebuild. - rebuild-dist.yml runs unprivileged on the PR (read-only token, no secrets, npm ci --ignore-scripts). Untrusted freshly-bumped dependency code executes here and only produces artifacts. - commit-dist.yml triggers via workflow_run so its definition comes from the default branch, downloads the built dist/ bytes, and commits them back to the PR branch using GITHUB_TOKEN. No PR code runs privileged. Also correct two stale comments in check-dist.yml that described Dependabot actor-gating that was never implemented. Co-authored-by: Copilot App <[email protected]>
There was a problem hiding this comment.
Pull request overview
Adds an automated, two-workflow pipeline to rebuild and auto-commit dist/ for Dependabot direct production dependency bumps, keeping the checked-in transpiled output up to date without a manual rebuild while maintaining a privilege boundary between build and push.
Changes:
- Adds an unprivileged
pull_requestworkflow (rebuild-dist.yml) to rebuilddist/for eligible Dependabot PRs and upload the rebuilt bytes + PR context as an artifact. - Adds a privileged
workflow_runworkflow (commit-dist.yml) to download the artifact, verify the PR head SHA hasn’t moved, then commit/push the rebuiltdist/to the PR branch. - Updates
check-dist.ymlcomments to accurately describe how Dependabot dist rebuilds are handled.
Show a summary per file
| File | Description |
|---|---|
| .github/workflows/rebuild-dist.yml | New unprivileged builder that rebuilds dist/ for Dependabot direct-production bumps and uploads an artifact when changes are detected. |
| .github/workflows/commit-dist.yml | New privileged consumer that downloads the artifact, verifies the PR head SHA, then commits/pushes rebuilt dist/ back to the Dependabot branch. |
| .github/workflows/check-dist.yml | Comment-only updates to reflect the new automatic rebuild/commit behavior for Dependabot production dependency bumps. |
Review details
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 3/3 changed files
- Comments generated: 2
- Review effort level: Low
| permissions: | ||
| contents: read | ||
|
|
There was a problem hiding this comment.
Good catch — added pull-requests: read to the workflow permissions in a4018e9 so fetch-metadata can read the PR.
| run: | | ||
| if [ -d incoming/dist ] && [ -f incoming/dist-meta/head-ref ]; then | ||
| echo "present=true" >> "$GITHUB_OUTPUT" | ||
| echo "head-ref=$(cat incoming/dist-meta/head-ref)" >> "$GITHUB_OUTPUT" | ||
| echo "head-sha=$(cat incoming/dist-meta/head-sha)" >> "$GITHUB_OUTPUT" | ||
| else | ||
| echo "present=false" >> "$GITHUB_OUTPUT" | ||
| fi |
There was a problem hiding this comment.
Agreed. Fixed in a4018e9: the step now reads only the first line, strips CR/LF, and validates head-sha against ^[0-9a-f]{40}$ and head-ref against a safe charset before writing to $GITHUB_OUTPUT — failing closed otherwise. It also requires head-sha to exist before use. Since the values originate from the unprivileged PR-context artifact, this closes the injection vector.
- Grant pull-requests: read in rebuild-dist.yml, required by dependabot/fetch-metadata. - Harden commit-dist.yml: strictly validate head-ref/head-sha read from the PR-context artifact (40-hex sha, safe ref charset, single line) and require head-sha to exist before use, preventing workflow-command injection into $GITHUB_OUTPUT. Co-authored-by: Copilot App <[email protected]>
What
Adds a two-workflow pipeline that automatically rebuilds the transpiled
dist/whenever Dependabot bumps a production dependency, so consumers of the action get the updated JavaScript without waiting on a manual rebuild.Why
Today
check-dist.ymldetects a staledist/and fails the PR, but a human still has to rebuild and push. Every Dependabot prod-dep bump goes red until someone does that by hand.How
Split across two workflows so untrusted, freshly-bumped dependency code never runs in a job that holds a write token:
rebuild-dist.yml— runs unprivileged on the PR (contents: read, no secrets,npm ci --ignore-scripts). Gated togithub.actor == 'dependabot[bot]'anddependency-type == 'direct:production'viadependabot/fetch-metadata. Rebuildsdist/; if it changed, uploads the bytes plus PR context (head-ref/head-sha) as an artifact.commit-dist.yml— triggers onworkflow_run, so the workflow definition executed here is the trusted copy frommain. Downloads the artifact, checks out the PR branch, verifies the head SHA has not moved (race guard), drops in the rebuiltdist/, and commits/pushes with the defaultGITHUB_TOKEN(contents: write).Only already-built bytes cross the privilege boundary — no PR-authored or dependency code executes with a write credential.
Notes / trade-offs
GITHUB_TOKEN(no GitHub App to manage). Consequence: the push does not re-triggercheck-dist.yml, so the rebuiltdist/is not independently re-verified. TheVerify head is unchangedguard plus the builder's own byte diff are the integrity checks. Swapping to a short-lived App token would restore automatic re-verification if desired.check-dist.ymlthat described Dependabot actor-gating which was never actually implemented.workflows: ["Rebuild dist"]incommit-dist.ymlmust stay in sync with thename:ofrebuild-dist.yml.