From 6d53e1155c98e979e14bdce4b4c1fbe9f2cd8ea0 Mon Sep 17 00:00:00 2001 From: Deepak Modi <118504803+deepakmodidev@users.noreply.github.com> Date: Tue, 7 Jul 2026 21:34:07 +0530 Subject: [PATCH] [codex] Guard latest desktop release (#2463) * ci: guard latest desktop release * fix(ci): self-heal a hijacked releases/latest pointer (#2462) The guard previously only detected a hijacked latest pointer and failed the run, still requiring a maintainer with delete rights to remove the junk release by hand. Add a remediation step that, on scheduled/manual runs, deletes a release carrying the unambiguous hijack signature -- published, non-prerelease, a non-semver tag, and zero electron-updater feed assets (latest.yml / latest-mac.yml / latest-linux.yml) -- along with its tag, so GitHub re-points latest to the newest real stable release. A real stable release always has a semver tag and the feed assets, so this can never match one; an in-progress real release keeps its semver tag, so it is excluded too, and the step never runs on `release` events to avoid racing an asset upload. This lets merging the PR resolve #2462 via the Actions token with no manual admin action, while the verify step stays as the tripwire for anything outside the auto-remediable signature. Bumps the job permission from contents:read to contents:write for the delete. * Revert "fix(ci): self-heal a hijacked releases/latest pointer (#2462)" This reverts commit 185bc563a953eccd93b5dd27a37f9fbd76009b74. --- .github/workflows/release-latest-guard.yml | 57 ++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 .github/workflows/release-latest-guard.yml diff --git a/.github/workflows/release-latest-guard.yml b/.github/workflows/release-latest-guard.yml new file mode 100644 index 000000000..8acf55daf --- /dev/null +++ b/.github/workflows/release-latest-guard.yml @@ -0,0 +1,57 @@ +name: Release latest guard + +# Guard against accidental non-stable GitHub releases taking over +# /releases/latest and breaking electron-updater's stable channel. +on: + release: + types: [published, edited, released] + schedule: + - cron: "17 * * * *" + workflow_dispatch: + +jobs: + latest-release: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Verify latest stable release + env: + GH_TOKEN: ${{ github.token }} + REPO: ${{ github.repository }} + shell: bash + run: | + set -euo pipefail + + release_json="$(gh release view --repo "$REPO" --json tagName,isPrerelease,isDraft,assets)" + tag="$(jq -r '.tagName' <<<"$release_json")" + prerelease="$(jq -r '.isPrerelease' <<<"$release_json")" + draft="$(jq -r '.isDraft' <<<"$release_json")" + + echo "Latest release: $tag" + + if [[ "$draft" == "true" || "$prerelease" == "true" ]]; then + echo "::error::GitHub latest resolved to draft/prerelease '$tag'; stable latest must be a published non-prerelease." + exit 1 + fi + + if [[ ! "$tag" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "::error::GitHub latest resolved to '$tag'; expected a stable semver tag like v0.10.2." + exit 1 + fi + + mapfile -t assets < <(jq -r '.assets[].name' <<<"$release_json") + printf 'Assets:\n%s\n' "${assets[@]}" + + required_assets=( + latest.yml + latest-mac.yml + latest-linux.yml + ) + + for required in "${required_assets[@]}"; do + if ! printf '%s\n' "${assets[@]}" | grep -Fxq "$required"; then + echo "::error::Latest stable release '$tag' is missing updater feed asset '$required'." + exit 1 + fi + done