Skip to content

Commit 99b86bd

Browse files
committed
docs: lead CI check with unprivileged pull_request, harden fork-comment workflow for checkout@v7
Invert the PR-check recommendation: the default is now a single `ci check` step in the existing pull_request workflow (forks get the check but no comment), with pull_request_target demoted to an opt-in "Commenting on fork PRs" section. Apply the actions/checkout@v7 fork-checkout change (enforced now, backported to floating tags on July 16, 2026): bump checkouts to @v7 and add allow-unsafe-pr-checkout: true on the read-only ./pr checkout in both the recommended workflow and our own dogfooding bumpy-check.yaml. Without this the fork-comment workflow would start failing once the backport lands. - docs/github-actions.md: restructure + flag fix + 3rd security rule - docs/prereleases.md: generalize the now-non-default check trigger note - .github/workflows/bumpy-check.yaml: @v7 + flag on the fork job
1 parent 4f1b5b6 commit 99b86bd

3 files changed

Lines changed: 33 additions & 21 deletions

File tree

.github/workflows/bumpy-check.yaml

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@ jobs:
2828
if: github.event.pull_request.head.repo.full_name == github.repository
2929
runs-on: ubuntu-latest
3030
steps:
31-
- uses: actions/checkout@v6
31+
# Same-repo PR head — not a fork, so checkout@v7's fork-checkout block
32+
# doesn't apply here and no allow-unsafe-pr-checkout flag is needed.
33+
- uses: actions/checkout@v7
3234
with:
3335
ref: ${{ github.event.pull_request.head.sha }}
3436
fetch-depth: 0 # history to diff bump files against the PR base branch
@@ -49,16 +51,19 @@ jobs:
4951
runs-on: ubuntu-latest
5052
steps:
5153
# TRUSTED base checkout (main) — bumpy is built and run from here.
52-
- uses: actions/checkout@v6
54+
- uses: actions/checkout@v7
5355
with:
5456
ref: main
5557
persist-credentials: false
5658
# UNTRUSTED PR head into ./pr — only READ via --cwd, never built or run.
57-
- uses: actions/checkout@v6
59+
# checkout@v7 blocks fork-PR checkout under pull_request_target by default;
60+
# allow-unsafe-pr-checkout opts in — safe because ./pr is never run.
61+
- uses: actions/checkout@v7
5862
with:
5963
ref: ${{ github.event.pull_request.head.sha }}
6064
path: pr
6165
persist-credentials: false
66+
allow-unsafe-pr-checkout: true
6267
- uses: oven-sh/setup-bun@v2
6368
- run: bun install
6469
- run: bun run --filter @varlock/bumpy build

docs/github-actions.md

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,23 @@ These commands facilitate the following:
1616
>
1717
> The version-resolution shell snippets work as-is regardless of package manager — they only depend on `jq` and `git`, both preinstalled on GitHub-hosted runners.
1818
19-
## PR check workflow
19+
## PR check
2020

21-
Posts/updates the release-plan comment on every PR, including PRs from forks. **Copy it as-is and you're covered** — it's structured so nothing in a fork PR can influence how bumpy is fetched or run. (If you change `main` to your own base branch, that's the only edit most repos need. See the security notes below before restructuring it.)
21+
`bumpy ci check` confirms every PR carries a bump file and posts a release-plan comment showing what will be released. The simplest setup is one step in your existing PR workflow (or a new one triggered `on: pull_request`):
22+
23+
```yaml
24+
- run: bunx @varlock/bumpy ci check
25+
env:
26+
GH_TOKEN: ${{ github.token }}
27+
```
28+
29+
Give the job `permissions: pull-requests: write`. This runs in the ordinary `pull_request` context — the same trust level as the rest of your CI — so none of the privileged-workflow precautions in the next section apply: you can `bun install` and run bumpy from your devDeps like any other CLI. (If the job already ran `bun install`, `bunx` picks up your pinned version from `node_modules`; otherwise it fetches the latest.)
30+
31+
**Fork PRs get the check, but not the comment.** GitHub hands `pull_request` runs from forks a **read-only token and no secrets**, so the comment can't be posted there. `ci check` still runs and fails the job (red ✗) on a missing bump file, with the explanation in the job logs — forks stay gated correctly, you just don't get the rendered comment. For most repos that's the right trade, and it keeps you off `pull_request_target` entirely. If you do want the comment on fork PRs too, set up the dedicated workflow below.
32+
33+
## Commenting on fork PRs
34+
35+
Posting to a PR needs a **write** token, and GitHub only grants one to a fork PR through a privileged trigger — `pull_request_target`. That trigger runs with your write token and secrets **even though the PR is code you don't control**, so the workflow must be structured so nothing in the fork PR can influence what gets fetched or run. The one below does that — copy it as-is (changing only `main` if your base branch differs), and read the security notes before restructuring it.
2236

2337
```yaml
2438
# .github/workflows/bumpy-check.yaml
@@ -38,17 +52,21 @@ jobs:
3852
# here, so the package-manager config in effect (bunfig.toml, .npmrc)
3953
# is YOURS, not the PR's. Hardcoded "main" (the PR controls its own
4054
# base ref); change it to your base branch.
41-
- uses: actions/checkout@v6
55+
- uses: actions/checkout@v7
4256
with:
4357
ref: main
4458
persist-credentials: false
4559
4660
# 2. UNTRUSTED PR head into ./pr — only READ from here, never run it.
47-
- uses: actions/checkout@v6
61+
# actions/checkout@v7 refuses to fetch fork PR code under
62+
# pull_request_target by default; `allow-unsafe-pr-checkout` opts back
63+
# in. It's safe HERE only because ./pr is never installed or run.
64+
- uses: actions/checkout@v7
4865
with:
4966
ref: ${{ github.event.pull_request.head.sha }}
5067
path: pr
5168
persist-credentials: false
69+
allow-unsafe-pr-checkout: true
5270

5371
- uses: oven-sh/setup-bun@v2
5472

@@ -69,10 +87,11 @@ jobs:
6987
7088
### ⚠️ Security essentials
7189
72-
`pull_request_target` carries a **write token and secrets even on fork PRs** — that's what lets it comment on forks, and why a PR author must never be able to influence what runs. The workflow above handles this; two rules to preserve if you adapt it:
90+
`pull_request_target` carries a **write token and secrets even on fork PRs** — that's what lets it comment on forks, and why a PR author must never be able to influence what runs. The workflow above handles this; three rules to preserve if you adapt it:
7391

7492
- **Never execute PR code** — no `bun install` / `npm install` (postinstall scripts run), no `bun run <script>` / `npm test`, no building from the PR tree.
7593
- **Fetch and run bumpy from the trusted base checkout; only _read_ the PR tree** via `--cwd ./pr`. Keep `persist-credentials: false` on both checkouts.
94+
- **Keep `allow-unsafe-pr-checkout: true` on the `./pr` checkout only.** Since [June 2026](https://github.blog/changelog/2026-06-18-safer-pull_request_target-defaults-for-github-actions-checkout/), `actions/checkout` refuses to fetch fork PR code under `pull_request_target` unless you set this deliberately-conspicuous flag (enforced on `@v7` now, and backported to floating tags like `@v6` on July 16, 2026). It's correct here _because_ `./pr` is read-only data — never add it to a checkout whose code you then install or run.
7695

7796
As a guardrail, `bumpy ci check` **fails** if it runs under `pull_request_target` without an explicit `--cwd` — so an outdated single-checkout workflow surfaces loudly instead of silently staying exploitable. If the working directory is genuinely trusted (e.g. a same-repo, non-fork PR), pass `--cwd .` to acknowledge it. (This is a migration nudge, not a security boundary: a PR that actually hijacked resolution would be running its own bumpy. The fix is the two-checkout layout above.)
7897

@@ -118,18 +137,6 @@ You can also pin the version directly (`bunx @varlock/bumpy@1.2.3 ci check --cwd
118137

119138
</details>
120139

121-
### Don't need fork PR support?
122-
123-
If you don't care about posting comments on external/fork PRs (private repo, internal-only contributors, etc.), you can skip the separate workflow entirely. Just add a step to your existing `pull_request` CI workflow:
124-
125-
```yaml
126-
- run: bunx @varlock/bumpy ci check
127-
env:
128-
GH_TOKEN: ${{ github.token }}
129-
```
130-
131-
Make sure the job has `permissions: pull-requests: write`. Since `pull_request` runs in a non-privileged context, all the "no installs / no PR scripts" rules above don't apply — you can `bun install` and run bumpy from your devDeps like any other CLI. The trade-off: fork PRs won't get a comment (the check still runs and fails red on missing bump files, just without the helpful explanation).
132-
133140
## Release workflow (recommended: split jobs)
134141

135142
The recommended release workflow splits version-PR maintenance from publishing into separate jobs. Only the publish job carries `id-token: write` and npm credentials, and it runs inside a GitHub Environment — so a rogue workflow elsewhere in the repo can't request an OIDC token that npm will accept.

docs/prereleases.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ That's the only workflow change. `bumpy ci release` reads the current branch, lo
141141

142142
> Make sure the checkout step uses `fetch-depth: 0` (the [release workflow](github-actions.md) already requires this) — the channel publish trigger diffs the triggering push to detect release PR merges.
143143

144-
> The PR check workflow (`bumpy-check.yaml`) needs no changes — it runs on `pull_request_target` and handles any base branch.
144+
> The PR check needs no changes for channels — `bumpy ci check` detects the PR's base branch automatically, whether it runs as a step in your normal `pull_request` workflow or in the dedicated `pull_request_target` workflow for [fork-PR comments](github-actions.md#commenting-on-fork-prs).
145145

146146
---
147147

0 commit comments

Comments
 (0)