Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
d35592b
merge dev into main for the v2.32.1 release
lidge-jun Aug 25, 2026
71c57ea
release: v2.32.1
lidge-jun Aug 25, 2026
d560ac6
merge dev into main for the v2.33.0 release
lidge-jun Aug 25, 2026
08ada6f
Merge pull request #2553 from lidge-jun/codex/promote-main-2330
lidge-jun Aug 25, 2026
ec51e42
release: v2.33.0
lidge-jun Aug 25, 2026
e25b653
merge dev into main for the v2.34.0 release
lidge-jun Aug 27, 2026
80fff9a
Merge pull request #2760 from lidge-jun/codex/promote-main-2340
lidge-jun Aug 27, 2026
fc4de77
Merge pull request #2826 from lidge-jun/codex/promote-main-2350
lidge-jun Aug 28, 2026
c7d8407
Merge pull request #3002 from lidge-jun/codex/promote-main-2360
lidge-jun Aug 30, 2026
54e2274
Merge pull request #3037 from lidge-jun/codex/promote-main-2370
lidge-jun Aug 31, 2026
2c4dca1
merge dev into the promotion branch for v2.38.0
lidge-jun Aug 31, 2026
a34e8b7
merge dev into the promotion branch for v2.38.0 (picks up the ReDoS fix)
lidge-jun Aug 31, 2026
ebb4d55
Merge pull request #3073 from lidge-jun/codex/promote-main-2380
lidge-jun Aug 31, 2026
682112e
Merge remote-tracking branch 'origin/dev' into codex/promote-main-2390
lidge-jun Sep 1, 2026
af6113a
merge dev into main for the v2.39.0 release
lidge-jun Sep 1, 2026
847f4f1
merge dev into main for the v2.40.0 release
Sep 2, 2026
ac78647
Merge pull request #3261 from lidge-jun/codex/promote-main-2400
lidge-jun Sep 2, 2026
aaa9eaf
fix(release): pass the bump job's permissions through the reusable-wo…
lidge-jun Sep 2, 2026
35ff3a4
Merge pull request #3263 from lidge-jun/codex/promote-main-2400-relfix
lidge-jun Sep 2, 2026
5c4579a
fix(release): isolate dev data from bump credentials
luvs01 Sep 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 26 additions & 7 deletions .github/workflows/dev-version-bump.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,17 +62,27 @@ jobs:
# Open the pull request.
pull-requests: write
steps:
- name: Checkout dev
# Keep every executable file in the privileged job pinned to the audited
# release revision. The later dev checkout is input data only: none of its
# actions, dependencies, scripts, or tests run with this job's token.
- name: Checkout trusted automation
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
ref: dev
ref: ${{ github.sha }}
# Tags are load-bearing, not decoration: the freeness gate below is a bun
# test that reads the local tag set, and release-version-line.test.ts
# returns EARLY on an empty set. A shallow checkout would make that gate
# silently vacuous instead of failing loudly.
fetch-depth: 0
# Do NOT set persist-credentials: false here as the read-only workflows do.
# This job has to push its bump branch.
persist-credentials: false

- name: Checkout dev as data
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
ref: dev
path: dev-tree
fetch-depth: 0
persist-credentials: false

# The repository-owned composite action, not a hand-pinned setup-bun SHA: it
# resolves the Bun version from package.json so the runtime SOT stays in one
Expand All @@ -89,7 +99,7 @@ jobs:
RELEASED_VERSION: ${{ inputs.released-version }}
run: |
set -euo pipefail
bun scripts/bump-dev-version.ts "${RELEASED_VERSION}" package.json
bun scripts/bump-dev-version.ts "${RELEASED_VERSION}" dev-tree/package.json

- name: Prove the chosen version is unused
if: ${{ steps.decide.outputs.changed == 'true' }}
Expand All @@ -98,10 +108,14 @@ jobs:
# of the tag set, so it is settled here by the detector that already owns the
# question. If this fails, no pull request is opened and the job goes red asking
# for a human decision - which is the correct outcome, not a fallback.
run: bun test tests/release-version-line.test.ts
run: |
# Exercise the trusted detector against the candidate package metadata.
cp dev-tree/package.json package.json
bun test tests/release-version-line.test.ts

- name: Open the bump pull request
if: ${{ steps.decide.outputs.changed == 'true' }}
working-directory: dev-tree
env:
GH_TOKEN: ${{ github.token }}
NEXT_VERSION: ${{ steps.decide.outputs.version }}
Expand Down Expand Up @@ -153,7 +167,12 @@ jobs:
git checkout -b "${branch}"
git add package.json
git commit -m "fix(release): move dev to ${NEXT_VERSION} after ${RELEASED_VERSION}"
git push origin "${branch}"
# Supply the write credential only to this trusted push invocation. In
# particular, never persist it in the dev checkout while dev-controlled
# files could execute.
auth_header="$(printf 'x-access-token:%s' "${GH_TOKEN}" | base64 -w0)"
git -c "http.https://github.com/.extraheader=AUTHORIZATION: basic ${auth_header}" \
push origin "${branch}"
fi

gh pr create \
Expand Down
8 changes: 8 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,14 @@ jobs:
bump-dev-version:
needs: publish
if: ${{ inputs.dry-run != true }}
# A reusable-workflow CALL cannot grant the callee more than the calling job holds,
# and GitHub refuses the whole run at startup when the called workflow's own job
# declares permissions the caller did not pass down ("startup_failure", runs
# 33615174183 / 33615177849 — the first dispatches since #3129 wired this call).
# The callee's job declares exactly these two; nothing else in this file gains them.
permissions:
contents: write
pull-requests: write
uses: ./.github/workflows/dev-version-bump.yml
with:
released-version: v${{ inputs.version }}
Expand Down
18 changes: 17 additions & 1 deletion tests/ci-workflows.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -730,6 +730,23 @@ describe("GitHub Actions hardening", () => {
expect(workflow).toContain("actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e");
expect(workflow).not.toMatch(/uses:\s+\S+@(?:v\d+|main|master)\b/);

// The post-release bump has write authority, but dev is a mutable integration
// branch rather than the audited release input. Executable automation must stay
// on the exact caller SHA, and checkout credentials must remain absent until the
// final trusted push invocation.
const bumpWorkflow = await readText(".github/workflows/dev-version-bump.yml");
expect(bumpWorkflow).toContain("- name: Checkout trusted automation");
expect(bumpWorkflow).toContain("ref: ${{ github.sha }}");
expect(bumpWorkflow).toContain("- name: Checkout dev as data");
expect(bumpWorkflow).toContain("path: dev-tree");
expect(count(bumpWorkflow, "persist-credentials: false")).toBe(2);
expect(bumpWorkflow).toContain(
'bun scripts/bump-dev-version.ts "${RELEASED_VERSION}" dev-tree/package.json',
);
expect(bumpWorkflow).toContain("cp dev-tree/package.json package.json");
expect(bumpWorkflow).toContain("working-directory: dev-tree");
expect(bumpWorkflow).toContain('git -c "http.https://github.com/.extraheader=AUTHORIZATION: basic ${auth_header}"');

// Workflow-dispatch inputs must reach shell code via env, never by direct
// interpolation into run: source (script-injection hardening).
const runBlocks = workflow.split(/\n {6,}- name: /).filter(block => block.includes("run: |"));
Expand Down Expand Up @@ -5261,4 +5278,3 @@ describe("gui exhaustive-deps suppression stays scoped and effective", () => {
expect(models).not.toContain("react-doctor-disable-next-line");
});
});

Loading