fix(ci)!: only a push to the caller's own repo may release - #16
Conversation
A workflow_run trigger's `branches: [main]` filter matches the triggering run's head branch, so a fork PR from a branch named main passed it. The release job then ran in the base repository with RELEASE_PAT, contents: write and id-token: write, checked out the fork's commit and ran its install and publish scripts. npm Trusted Publishing does not stop it: the OIDC claims name the right repository and workflow file. - release-reusable.yml: the job itself refuses a workflow_run that is not a successful push-triggered run of the caller's repository, protecting every caller; the header example shows the guard and a SHA pin. - release.yml (this repo's own release): same guard. - Pin every action by commit SHA (changesets/action@v2 was a branch) and let Renovate maintain the digests. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository: btravstack/tools/.coderabbit.yaml Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (6)
Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review. 📝 WalkthroughWalkthroughCI and release workflows now reference actions by commit SHA. Release workflows add checks for successful push runs from the expected repository. Renovate adds a preset for GitHub Action digest pinning. ChangesWorkflow hardening
Priority: ⬆️ High Estimated code review effort: 2 (Simple) | ~10 minutes Change: Bug fix Merge Risk: ⚪ Minimal · up to The release gates are intended to exclude fork pull-request runs, and no merge-blocking issue is established by the supplied evidence. The change is ready for normal merge checks. 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Copilot review overview
🟢 Approval recommended
The new workflow_run guards correctly restrict release execution to successful push runs from the same repository and the action SHA pinning reduces supply-chain risk without introducing functional regressions.
Review effort: Lite
Findings: None
What changed in this PR
This PR hardens the release pipeline against a workflow_run escalation where a fork PR (with a main-named branch) could trigger a release job in the base repo and execute attacker-controlled code with release credentials/OIDC, and it also standardizes GitHub Actions pinning to immutable SHAs.
Changes:
- Add a defensive
if:gate to only allow releases from successful push-triggered CI runs in the same repository (blocking fork PRworkflow_runabuse). - Pin GitHub Actions (
checkout,setup-node,cache,pnpm/action-setup,changesets/action) to commit SHAs. - Enable Renovate’s
helpers:pinGitHubActionDigeststo keep pinned action digests current automatically.
| File | Description |
|---|---|
| renovate.json | Enables Renovate helper to pin GitHub Actions by digest/SHA. |
| .github/workflows/release.yml | Adds release gating for workflow_run and pins actions used by the release workflow. |
| .github/workflows/release-reusable.yml | Adds “defense in depth” gating for callers and pins actions used by the reusable release workflow. |
| .github/workflows/ci.yml | Pins actions/checkout to a commit SHA for CI jobs. |
| .github/workflows/ci-reusable.yml | Pins commonly used actions (checkout, setup-node, cache, pnpm/action-setup) to commit SHAs across reusable CI jobs. |
| .github/actions/setup/action.yml | Pins pnpm/action-setup and actions/setup-node to commit SHAs in the composite setup action. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
btravstack/tools#16 adds the same guard inside release-reusable.yml. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
btravstack/tools#16 adds the same guard inside release-reusable.yml. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Vulnerability
release-reusable.yml's documented caller pattern, used by amqp-contract, temporal-contract, unthrown and this repo's ownrelease.yml, is:workflow_run'sbranches:filter matches the triggering run's head branch. CI also runs onpull_request, fork PRs included, so a fork PR from a branch namedmainpasses the filter.The release job then runs in the base repository with
RELEASE_PAT,contents: writeandid-token: write. It checks outworkflow_run.head_sha, which is the fork's commit, and runs that commit's install andreleasescripts. npm Trusted Publishing does not stop this: the OIDC claims name the right repository and workflow file, so the tarball would even carry provenance.The only gate is the fork-PR approval policy, and with
first_time_contributorsit disappears after one merged PR.Fix
release-reusable.yml: the job refuses anyworkflow_runthat isn't a successful push-triggered run of the caller's own repository. A called workflow sees its caller'sgithubcontext, so this protects every caller once it picks up this version, even one that never updates its ownif:. Other triggers such asworkflow_dispatchare unaffected. The header example now shows the guard and a SHA pin.release.yml(this repo's own release): same guard.changesets/action@v2was a branch, not a tag. Renovatehelpers:pinGitHubActionDigestskeeps the digests current.actionlint passes. The only output is pre-existing shellcheck style notes in
ci-reusable.yml.After merge
workflows-v1to the merge commit so existing@workflows-v1callers get the guard.workflows-*tags with a ruleset so only admins can move them.🤖 Generated with Claude Code
Summary by CodeRabbit