Skip to content

fix: guard release tag creation - #843

Open
sozercan wants to merge 26 commits into
mainfrom
fix/release-guardrails
Open

fix: guard release tag creation#843
sozercan wants to merge 26 commits into
mainfrom
fix/release-guardrails

Conversation

@sozercan

@sozercan sozercan commented Aug 8, 2026

Copy link
Copy Markdown
Member

Summary

  • replace the overloaded release workflow with explicit Prepare release and Publish release workflows
  • create preparation PRs with a separate non-bypass release-automation App so their pull-request checks run automatically
  • keep App credentials out of release-branch-controlled code by using credential-free checkout, trusted manifest edits, and delayed token creation
  • require the preparation PR itself to change every version field from its base to the requested version and to pass lint, unit-test, and all other latest workflow runs
  • create immutable release tags only after manifest/provenance validation and prod approval, using a dedicated least-privilege release App
  • reject decreasing patch versions within a release line while allowing older-line maintenance releases without regressing image or GitHub latest
  • revalidate version policy and tag provenance before artifact and runner-image publishing, and remove manual runner publishing
  • synchronize newer release lines back to main through the release-automation App, a canonical monotonic branch, exact-SHA rechecks, and force-with-lease protection
  • support recovery releases such as v0.22.1 when an unusable .0 tag must remain immutable
  • document the operator flow and add fail-closed validator, version-policy, and sync-planner tests

Validation

  • scripts/ci/validate-release-version_test.sh
  • scripts/ci/validate-release_test.sh
  • scripts/ci/plan-release-sync_test.sh
  • actionlint on all affected workflows
  • shellcheck on all release validator/planner scripts and tests
  • exact-SHA lease creation, update, and stale-writer rejection against a temporary bare remote
  • independent credential-boundary and release-policy reviews
  • live history: the accidental v0.22.0 candidate is rejected because its manifests contain v0.21.0; v0.22.1 is rejected because preparation PR chore: Prepare v0.22.1 release #842 was merged without any completed checks

Required repository setup

Before using Prepare release or Publish release:

  • install a release GitHub App on this repository with only Contents: write
  • store RELEASE_APP_CLIENT_ID as an environment variable and RELEASE_APP_PRIVATE_KEY as an environment secret in prod; restrict prod to main and retain its required reviewer/no-admin-bypass policy
  • install a separate release-automation App with Contents: write and Pull requests: write, with no tag-ruleset bypass
  • create a release-automation environment restricted to main, then store RELEASE_AUTOMATION_APP_CLIENT_ID as an environment variable and RELEASE_AUTOMATION_APP_PRIVATE_KEY as an environment secret there
  • replace the current repository-role bypass on the v* tag-creation ruleset with an Always allow bypass only for the release App
  • retain the separate no-bypass update/deletion ruleset so published tags remain immutable
  • backport the publisher workflows and validator to any still-active release branch created before this PR; tag workflows are loaded from the tagged commit

Signed-off-by: Sertac Ozercan <sozercan@gmail.com>
Copilot AI balanced review requested due to automatic review settings August 8, 2026 22:18

Copilot AI 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.

Pull request overview

Separates release preparation from publishing and guards immutable release tags with provenance validation, production approval, and a least-privilege GitHub App.

Changes:

  • Adds prepare, publish, and post-release version-sync workflows.
  • Adds and tests fail-closed release provenance validation.
  • Applies validation to publishers and documents protected repository setup.
Show a summary per file
File Description
.github/workflows/prepare-release.yaml Creates version preparation PRs.
.github/workflows/publish-release.yaml Validates and creates protected tags.
.github/workflows/sync-released-version.yaml Syncs minor releases to main.
.github/workflows/release.yaml Adds artifact publication preflight.
.github/workflows/release-runners.yaml Guards runner image publishing.
.github/workflows/release-pr.yaml Removes the overloaded legacy workflow.
.github/workflows/unit-test.yaml Runs validator tests in CI.
scripts/ci/validate-release.sh Validates manifests and release provenance.
scripts/ci/validate-release_test.sh Covers validator acceptance and rejection paths.
CONTRIBUTING.md Documents protected release administration.
website/docs/release.md Updates user-facing release instructions.

Review details

Tip

Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

  • Files reviewed: 11/11 changed files
  • Comments generated: 0
  • Review effort level: Balanced

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4b3263b455

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/workflows/sync-released-version.yaml Outdated
Comment thread .github/workflows/sync-released-version.yaml Outdated
Signed-off-by: Sertac Ozercan <sozercan@gmail.com>
Copilot AI review requested due to automatic review settings August 8, 2026 23:57

Copilot AI 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.

Review details

  • Files reviewed: 12/12 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3f3fd968f2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/workflows/prepare-release.yaml
Signed-off-by: Sertac Ozercan <sozercan@gmail.com>
Copilot AI review requested due to automatic review settings August 9, 2026 00:04
@sozercan

sozercan commented Aug 9, 2026

Copy link
Copy Markdown
Member Author

@codex review

Copilot AI 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.

Review details

Suppressed comments (1)

CONTRIBUTING.md:297

  • The workflows read each client ID from the vars context and each private key from secrets, but these setup steps do not distinguish environment variables from environment secrets. An operator who stores both values as secrets will leave vars.RELEASE_APP_CLIENT_ID/vars.RELEASE_SYNC_APP_CLIENT_ID empty and token creation will fail. State the storage type explicitly.
- Store `RELEASE_APP_CLIENT_ID` and `RELEASE_APP_PRIVATE_KEY` only in the protected `prod` environment. Require a reviewer on `prod`, disallow administrator bypass, and restrict deployments to `main`. Enable self-review prevention when a second maintainer or reviewer team is available.
- Store `RELEASE_SYNC_APP_CLIENT_ID` and `RELEASE_SYNC_APP_PRIVATE_KEY` only in a separate `version-sync` environment. Restrict it to `main` and disallow administrator bypass. It does not need another reviewer because it runs only after the approved tag job succeeds.
  • Files reviewed: 12/12 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread scripts/ci/validate-release.sh Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2231d4df85

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/workflows/publish-release.yaml
Signed-off-by: Sertac Ozercan <sozercan@gmail.com>
Copilot AI review requested due to automatic review settings August 9, 2026 00:23

Copilot AI 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.

Review details

Suppressed comments (2)

scripts/ci/validate-release_test.sh:134

  • This fixture omits the fourth head.sha field, so the validator fails on malformed PR data before reaching the intended merge-ancestry check. Supply a valid head SHA so the test actually proves that an unrelated merge commit is rejected.
unrelated_pr_details=$(printf 'true\trelease-1.2\t%s' "$side_commit")

.github/workflows/prepare-release.yaml:88

  • create-pull-request only calls GitHub's add-labels endpoint; it does not create missing repository labels. Since this label is a new version on every release (the repository currently has version labels only through v0.22.1), preparing the next version will get a 422 while applying the label, and the resulting unlabeled PR cannot pass the validator. Either create the version label first with the required permission and document that setup, or remove the dynamic-label dependency and bind provenance to the requested version another way.
          labels: |
            release-pr
            ${{ inputs.release_version }}
  • Files reviewed: 12/12 changed files
  • Comments generated: 2
  • Review effort level: Balanced

Comment thread scripts/ci/validate-release.sh Outdated
Comment thread .github/workflows/prepare-release.yaml Outdated
Signed-off-by: Sertac Ozercan <sozercan@gmail.com>
Signed-off-by: Sertac Ozercan <sozercan@gmail.com>
Copilot AI review requested due to automatic review settings August 9, 2026 00:54

Copilot AI 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.

Review details

Suppressed comments (3)

scripts/ci/plan-release-sync.sh:80

  • The planner likewise accepts version:1.2.3 and appVersion:v1.2.3 because * permits no YAML separator. It can report a malformed chart as consistent and continue synchronization planning. Require at least one whitespace character after each colon.
  chart_version_count=$(awk '/^version:[[:space:]]*/ { count++ } END { print count + 0 }' "$chart_path")
  chart_app_version_count=$(awk '/^appVersion:[[:space:]]*/ { count++ } END { print count + 0 }' "$chart_path")

scripts/ci/validate-release.sh:73

  • These checks allow zero whitespace after :, so a line such as version:1.2.3 is accepted as the expected version even though YAML treats it as a scalar rather than the Helm chart's version key. The provenance validator can therefore approve a malformed Chart.yaml and create a tag whose chart publication fails. Require at least one separator character for both fields.
  chart_version_count=$(awk '/^version:[[:space:]]*/ { count++ } END { print count + 0 }' <<<"$chart_content")
  chart_app_version_count=$(awk '/^appVersion:[[:space:]]*/ { count++ } END { print count + 0 }' <<<"$chart_content")

scripts/ci/plan-release-sync.sh:120

  • Release components are accepted with unbounded digits, but these arithmetic comparisons use Bash's signed machine-width integers. For example, v9223372036854775808.0.0 is valid under parse_version but overflows here and can be planned as older than v1.0.0. Compare decimal components by length and then lexically, as validate-release-version.sh does, and use the same comparison when selecting the pending target.
  if ((left_major > right_major || (left_major == right_major && left_minor > right_minor))); then
    comparison=1
  elif ((left_major < right_major || (left_major == right_major && left_minor < right_minor))); then
    comparison=-1
  • Files reviewed: 14/14 changed files
  • Comments generated: 2
  • Review effort level: Balanced

Comment thread .github/workflows/release.yaml Outdated
Comment thread .github/workflows/release-runners.yaml Outdated
Signed-off-by: Sertac Ozercan <sozercan@gmail.com>
Copilot AI review requested due to automatic review settings August 9, 2026 01:12

Copilot AI 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.

Review details

Suppressed comments (2)

scripts/ci/plan-release-sync.sh:120

  • The accepted semantic-version syntax allows arbitrarily long numeric components, but Bash arithmetic is signed machine-width. For example, with main at v9223372036854775807.0.0, release v9223372036854775808.0.0 overflows and is treated as older, so the required sync is skipped. The pending-version comparisons below have the same problem. Use the length-then-lexicographic component comparison already used by validate-release-version.sh rather than (( ... )) for version components.
  if ((left_major > right_major || (left_major == right_major && left_minor > right_minor))); then
    comparison=1
  elif ((left_major < right_major || (left_major == right_major && left_minor < right_minor))); then
    comparison=-1

.github/workflows/prepare-release.yaml:182

  • The version label is dynamic, but create-pull-request@v8.1.1 only calls issues.addLabels; it does not create missing repository labels. The repository currently has labels through v0.22.1 but no future version labels, and the documented setup does not require operators to create one, so preparing the next version will create an unlabeled PR and then fail while applying the label. Since validate-release.sh also requires this label, either create it explicitly before the PR or stop using per-version labels and identify candidates from the static release-pr label plus the existing manifest checks.
          labels: |
            release-pr
            ${{ inputs.release_version }}
  • Files reviewed: 14/14 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread .github/workflows/publish-release.yaml Outdated
Signed-off-by: Sertac Ozercan <sozercan@gmail.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Keep it up!

Reviewed commit: 0c942c32c1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Copilot AI 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.

Review details

Suppressed comments (2)

.github/workflows/release.yaml:164

  • An existing release is accepted solely by tag lookup. If that release is a draft or prerelease, this workflow still succeeds, but reconcile-app later requires .draft == false and .prerelease == false, so the immutable image tag can never be promoted until someone manually repairs the release. Validate the existing release as a published stable release here (or convert it explicitly) before reporting success.
          if gh api \
            "repos/${GITHUB_REPOSITORY}/releases/tags/${GITHUB_REF_NAME}" \
            --silent 2>/dev/null; then
            echo "GitHub release ${GITHUB_REF_NAME} already exists." >>"${GITHUB_STEP_SUMMARY}"
          else
            gh release create "${GITHUB_REF_NAME}" \

.github/workflows/release.yaml:154

  • This new failure point remains after Publish Helm chart, so a failed publisher can already have added the public vX.Y.Z chart to GitHub Pages; even on success, that chart is visible before the reconciler creates the corresponding immutable image tag. This contradicts the PR's guarantee that failed publisher attempts cannot change public versions. Stage the chart as an attempt-bound artifact and publish it only from the promotion path after the immutable image version is ready.

This issue also appears on line 159 of the same file.

      - name: Create GitHub release
  • Files reviewed: 25/25 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

Signed-off-by: Sertac Ozercan <sozercan@gmail.com>
Copilot AI review requested due to automatic review settings August 9, 2026 05:53

Copilot AI 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.

Review details

  • Files reviewed: 25/25 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread scripts/ci/validate-release-version.sh
Signed-off-by: Sertac Ozercan <sozercan@gmail.com>
Copilot AI review requested due to automatic review settings August 9, 2026 06:03

Copilot AI 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.

Review details

  • Files reviewed: 25/25 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread .github/workflows/publish-release.yaml
@sozercan

sozercan commented Aug 9, 2026

Copy link
Copy Markdown
Member Author

@codex review

Signed-off-by: Sertac Ozercan <sozercan@gmail.com>
Copilot AI review requested due to automatic review settings August 9, 2026 06:14
@sozercan

sozercan commented Aug 9, 2026

Copy link
Copy Markdown
Member Author

@codex review

Copilot AI 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.

Review details

  • Files reviewed: 25/25 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3eb7a3e616

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread scripts/ci/select-latest-release.sh
Comment thread .github/workflows/reconcile-release-latest.yaml
Signed-off-by: Sertac Ozercan <sozercan@gmail.com>
Copilot AI review requested due to automatic review settings August 9, 2026 07:03
@sozercan

sozercan commented Aug 9, 2026

Copy link
Copy Markdown
Member Author

@codex review

Copilot AI 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.

Review details

Suppressed comments (2)

.github/workflows/reconcile-release-latest.yaml:985

  • The runner exact-promotion path has the same unrecoverable stale-revision state: after the successful runner publisher triggers this job, a main advance makes its write step fail; rerunning preserves the stale github.sha, and manual dispatch skips this job. The manual global reconciler then refuses to proceed because the immutable runner tags were never fully created. Provide a current-main manual recovery mode that securely revalidates and promotes the selected successful runner attempt without requiring the already-successful publisher to be rerun.
    if: >-
      github.ref == 'refs/heads/main' &&
      github.event_name == 'workflow_run' &&
      github.event.workflow_run.name == 'Release runner images' &&
      github.event.workflow_run.conclusion == 'success'

scripts/ci/check-release-alias-floor.sh:294

  • The legacy fallback checks the DSSE predicate URI as critical.type, but actual Cosign simple-signing payloads use cosign container image signature there (the URI is a predicate type). Legacy signatures without annotations may also serialize optional as null, not {}. Consequently, the existing pre-guardrail latest image cannot establish a floor, so the first protected reconciliation fails instead of advancing the alias. Validate the real simple-signing payload shape and update the legacy fixtures accordingly.
  jq -e \
    --arg digest "$digest" \
    --arg image "ghcr.io/${ghcr_repository}@${digest}" '
      type == "array" and
      any(.[];
        .critical.type == "https://sigstore.dev/cosign/sign/v1" and
        .critical.image["docker-manifest-digest"] == $digest and
        .critical.identity["docker-reference"] == $image and
        .optional == {}
      )
  • Files reviewed: 28/28 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread .github/workflows/reconcile-release-latest.yaml
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Already looking forward to the next diff.

Reviewed commit: 7a80b4bf08

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Signed-off-by: Sertac Ozercan <sozercan@gmail.com>
Copilot AI review requested due to automatic review settings August 9, 2026 07:16
@sozercan

sozercan commented Aug 9, 2026

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. More of your lovely PRs please.

Reviewed commit: 9f206fe457

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Copilot AI 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.

Review details

  • Files reviewed: 28/28 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread scripts/ci/check-release-alias-floor.sh
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.

2 participants