Skip to content

ci: keep EKSCTLBOT_TOKEN out of the release-notes rendering job - #8862

Closed
sapphirew wants to merge 1 commit into
eksctl-io:mainfrom
sapphirew:ci/isolate-eksctlbot-token
Closed

ci: keep EKSCTLBOT_TOKEN out of the release-notes rendering job#8862
sapphirew wants to merge 1 commit into
eksctl-io:mainfrom
sapphirew:ci/isolate-eksctlbot-token

Conversation

@sapphirew

@sapphirew sapphirew commented Sep 11, 2026

Copy link
Copy Markdown
Collaborator

Note

Follows #8860, now merged. Rebased onto it, so this is a single commit touching only .github/workflows/release-drafter.yaml.

Description

The Draft Release Notes workflow runs as a single job that both handles the release-drafter output and holds EKSCTLBOT_TOKEN. Those two things do not need to be in the same job.

EKSCTLBOT_TOKEN is the highest-value credential in the repo: start-release.yaml triggers on a tag push and passes it to publish-release.yaml, which runs GoReleaser and commits the Homebrew formula to weaveworks/homebrew-tap. Unlike GITHUB_TOKEN, a tag pushed with a PAT does start workflows. Meanwhile the same job does the string handling on steps.draft.outputs.body, which is assembled from merged pull request titles — so the least trustworthy input in the workflow sits in the job with the most reach.

#8860 fixed the shell quoting. This separates the two concerns structurally, so the rendering step has nothing valuable within reach even if something about it regresses later.

The split

render_release_draft update_release_draft_pr
permissions contents: write, pull-requests: read contents: read
EKSCTLBOT_TOKEN no yes
checkout none — it does not need a working tree yes, persist-credentials: false
drafter output renders it to files under RUNNER_TEMP, uploads as an artifact consumes it as file content only, via body-path

The second job never interpolates the drafter output into a run: step — it copies one file into docs/release_notes/ and hands another to create-pull-request as a path.

Supporting changes

  • tag_name promoted to a job output. It comes from tag-template: 'v$NEXT_MINOR_VERSION' in .github/release-drafter.yml and is only used in action inputs (commit-message, title), never in a shell.
  • PR body moves from inline body: to body-path:, rendered with printf in the first job. body-path has been available since create-pull-request v6; this repo is on v8.1.1.
  • add-paths: docs/release_notes scopes the commit to the notes file, so nothing else in the tree can end up in the PR.
  • pull-requests: writeread on the drafter job. .github/release-drafter.yml configures no autolabeler:, so nothing in this workflow applies labels; the PR's own labels are applied by create-pull-request in the second job with its own token. Please sanity-check this one — it's the only permission I reduced based on what the config does rather than what the workflow obviously needs, and it's a one-line revert if I have it wrong.
  • retention-days: 1 on the artifact, since it's only a job-to-job hand-off.
  • if: github.event.repository.fork == false stays on the first job; the second is gated transitively through needs.

Checklist

  • Added tests that cover your change (n/a — GitHub Actions workflow config)
  • Added/modified documentation as required (n/a)
  • Manually tested
  • Made sure the title of the PR is a good description that can go into the release notes
  • (Core team) Added labels for change area (e.g. area/nodegroup) and target version (e.g. version/0.12.0)

Testing

The PR body is the part most likely to regress silently, since it moved from a YAML template to printf. I rendered it both ways for the same tag and body and diffed them — the old template is read straight out of git show 8e1d569:.github/workflows/release-drafter.yaml:

IDENTICAL

Also confirmed the notes file lands at docs/release_notes/0.220.0.md for tag v0.220.0, and that a body containing - evil"; whoami; echo " (#124) is written verbatim with nothing executed.

Workflow YAML parses; job graph and permissions are as intended:

jobs: ['render_release_draft', 'update_release_draft_pr']
render_release_draft    perms={'contents': 'write', 'pull-requests': 'read'}  needs=None
update_release_draft_pr perms={'contents': 'read'}                           needs=render_release_draft

Not verifiable locally: the artifact hand-off and the create-pull-request invocation only exercise on a real push to main. If the artifact path is wrong the symptom is a failed "Copy release notes from Draft" step rather than a malformed PR, so it fails closed.

The Draft Release Notes workflow ran as a single job that both handled
the release-drafter output and held EKSCTLBOT_TOKEN. That token is what
starts the publish pipeline (start-release.yaml triggers on a tag push
and passes it to publish-release.yaml, which runs GoReleaser and commits
the Homebrew formula), so it is the highest-value credential in the repo
and it does not need to be present while the drafter output is being
turned into a file.

Split the job in two:

  render_release_draft   contents: write, pull-requests: read
                         Runs release-drafter, renders the notes file and
                         the pull request body into RUNNER_TEMP, uploads
                         them as an artifact. No EKSCTLBOT_TOKEN, and no
                         checkout -- it does not need the working tree.

  update_release_draft_pr  contents: read
                         Checks out, copies the rendered notes into
                         docs/release_notes/, opens the pull request with
                         EKSCTLBOT_TOKEN. Consumes the drafter output only
                         as file content, via `body-path`, so it is never
                         interpolated into a run step.

Other changes this requires:

- `tag_name` is promoted to a job output. It comes from the
  `tag-template: 'v$NEXT_MINOR_VERSION'` in .github/release-drafter.yml
  and is only used in action inputs, not in a shell.
- The pull request body moves from an inline `body:` to `body-path:`,
  rendered with printf. Verified byte-identical to what the previous YAML
  template produced for the same tag and body.
- `add-paths: docs/release_notes` scopes the commit to the notes file.
- `pull-requests: write` drops to `read` on the drafter job.
  .github/release-drafter.yml configures no `autolabeler:`, so nothing in
  this workflow applies labels; the pull request labels are applied by
  create-pull-request in the second job using its own token.
- `retention-days: 1` on the artifact, since it is only a job-to-job hand-off.

The `if: github.event.repository.fork == false` guard stays on the first
job; the second is gated transitively through `needs`.
@sapphirew

Copy link
Copy Markdown
Collaborator Author

Superseded by #8863, which combines this with #8861 into a single review (kept as two separate commits). Closing to keep the review in one place.

Carried over unchanged from here: the render_release_draft / update_release_draft_pr job split, body-path, add-paths, and the permission reductions.

@sapphirew sapphirew closed this Sep 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant