Skip to content

ci(release): keep the publish job's write token out of the checkout - #7502

Merged
jrusso1020 merged 1 commit into
mainfrom
fix/publish-tag-token-scope
Nov 10, 2026
Merged

jrusso1020 merged 1 commit into
mainfrom
fix/publish-tag-token-scope

Conversation

@jrusso1020

Copy link
Copy Markdown
Collaborator

What

The publish job's checkout no longer stores the job's write token in .git/config. "Create release tag", the only step that pushes, now receives GITHUB_TOKEN in its own step env and authenticates its own git calls.

Why

actions/checkout persists the job token by default, so every later step in the publish job could read a contents: write token from .git/config. That includes bun install and bun run build, which run dependency code. #7495 moved the Lambda zip build out of this job but noted that the npm package build still runs here with that stored token. This PR removes the stored token. The job's permissions themselves don't change.

Related work

Refs #7495, #7485

How

  • Publish checkout: sets persist-credentials: false.
  • "Create release tag":
    • Gets GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} in its own step env and fails fast if the token is empty.
    • Passes the token as the same http.https://github.com/.extraheader basic-auth header that actions/checkout uses. The header goes through GIT_CONFIG_COUNT/GIT_CONFIG_KEY_0/GIT_CONFIG_VALUE_0, so it applies only to that step's git commands, never touches .git/config, and stays out of process args. The encoded value is masked with ::add-mask::.
    • The tag logic itself (ls-remote, fetch, tag, push, retry verification) is unchanged.
  • Other steps: none of them talk to the remote with git. "Verify immutable release checkout", validate-release-channel.mjs and package-agent-plugin.mjs only read local refs, which fetch-depth: 0 already provides. Every gh step already passes GH_TOKEN in its own env.

Test plan

  • scripts/publish-workflow.test.mjs passes 8/8:
    • A new test checks that the publish checkout sets persist-credentials: false and that the job-level env has no secrets. It also checks that secrets.GITHUB_TOKEN is passed only as GITHUB_TOKEN to "Create release tag" and only as GH_TOKEN to any other step.
    • The local-repo tag test now installs a pre-push hook. It checks that the push carries the expected basic-auth header for a fake token, that the masked value is printed, and that .git/config has no extraheader afterwards. Retry and mismatch behavior are covered as before.
    • Removing persist-credentials: false fails the new test. Removing the header export fails both tag tests.
  • actionlint on publish.yml reports no errors.
  • Not exercised: a real release run. The workflow has no dry-run path, and I didn't cut a release to test it. On the next release, check that the Create release tag step pushes the tag.
  • Comments follow CONTRIBUTING.md.

— Rames Jusso

🤖 Generated with Claude Code

The publish job's checkout persisted its contents:write token in
.git/config, where every later step, including the package install and
build, could read it. Check out without persisting credentials and give
the token only to "Create release tag", the one step that pushes. It
authenticates its own git calls through step-scoped GIT_CONFIG_* env and
masks the encoded value. The gh steps keep their own GH_TOKEN.

The workflow test now covers the checkout flag, which steps may receive
the token and under what name, the auth header the tag push actually
carries, and that nothing is written to .git/config.

— Rames Jusso

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@heygengenesis heygengenesis Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: APPROVE · 0 blocker · 0 important · 0 nits · head 40dae2e

No findings. With persist-credentials: false, the write token is no longer stored in .git/config. The only step that reaches the remote with git is Create release tag, and it gets the same extraheader that checkout would have written, scoped to that step through GIT_CONFIG_*.

Scope
  • Read all of publish.yml at head. Every other git call in the publish job is local: git rev-parse in the verify step, git branch -r --contains in validate-release-channel.mjs, which is skipped on pull_request, and show/ls-tree/cat-file/archive in package-agent-plugin.mjs. The gh steps carry their own GH_TOKEN. The prerelease tag-push path never pushes.
  • Ran node --test scripts/publish-workflow.test.mjs locally at head: 8/8 pass.
  • All Actions runs on the head are green. The BLOCKED merge state is the review gate.
  • Not exercised: a real release push (as stated in the PR body).

— Genesis

@jrusso1020
jrusso1020 added this pull request to the merge queue Nov 10, 2026
Merged via the queue into main with commit 12d954a Nov 10, 2026
68 checks passed
@jrusso1020
jrusso1020 deleted the fix/publish-tag-token-scope branch November 10, 2026 17:12
@miguel-heygen miguel-heygen mentioned this pull request Nov 10, 2026
3 of 4 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant