mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
[codex] Bind release publishers to immutable tags (BUZZ-SEC-039) (#1914)
This commit is contained in:
@@ -133,17 +133,23 @@ jobs:
|
||||
VERSION: ${{ steps.release.outputs.version }}
|
||||
TAG: ${{ steps.release.outputs.tag }}
|
||||
run: |
|
||||
# Both workflows take the bare version (for the build) and the tag ref
|
||||
# (so the dispatch builds the tagged commit, not main).
|
||||
# Run the workflow itself at the immutable tag so github.sha, OIDC,
|
||||
# provenance, and the checked-out source all name the release commit.
|
||||
extra_args=()
|
||||
case "$DISPATCH" in
|
||||
release) WORKFLOW="release.yml" ;;
|
||||
docker) WORKFLOW="docker.yml" ;;
|
||||
helm-chart) WORKFLOW="helm-chart.yml" ;;
|
||||
push-gateway-helm-chart) WORKFLOW="push-gateway-helm-chart.yml" ;;
|
||||
helm-chart)
|
||||
WORKFLOW="helm-chart.yml"
|
||||
extra_args=(-f "ref=$TAG") ;;
|
||||
push-gateway-helm-chart)
|
||||
WORKFLOW="push-gateway-helm-chart.yml"
|
||||
extra_args=(-f "ref=$TAG") ;;
|
||||
*)
|
||||
echo "::error::Unhandled dispatch target: '$DISPATCH'"
|
||||
exit 1 ;;
|
||||
esac
|
||||
gh workflow run "$WORKFLOW" \
|
||||
--ref "$TAG" \
|
||||
-f version="$VERSION" \
|
||||
-f ref="$TAG"
|
||||
"${extra_args[@]}"
|
||||
|
||||
@@ -55,6 +55,8 @@ jobs:
|
||||
mobile:
|
||||
- 'mobile/**'
|
||||
- '.github/workflows/ci.yml'
|
||||
- name: Release workflow source contract
|
||||
run: scripts/test-release-ref-contract.sh
|
||||
|
||||
rust-lint:
|
||||
name: Rust Lint
|
||||
|
||||
@@ -18,22 +18,19 @@ name: Docker image
|
||||
# - push tags relay-v*.*.* → :{version} + :{major}.{minor} + :{major}
|
||||
# (+ :latest for stable, NOT for prereleases)
|
||||
# - pull_request → build only (no push), cache stays warm
|
||||
# - workflow_dispatch → manual canary (no inputs), or relay-tag rescue
|
||||
# dispatch with version+ref inputs (see below)
|
||||
# - workflow_dispatch → relay-tag rescue, dispatched at the tag itself
|
||||
#
|
||||
# Why workflow_dispatch carries version/ref inputs:
|
||||
# Why workflow_dispatch carries a version input:
|
||||
# auto-tag-on-release-pr-merge.yml pushes relay-v* with the default
|
||||
# GITHUB_TOKEN, which GitHub deliberately does NOT let fire on:push triggers
|
||||
# (recursion guard). So the push:tags trigger above never runs for releases.
|
||||
# auto-tag instead dispatches this workflow with the bare version + tag ref,
|
||||
# the same rescue release.yml already uses for the desktop lane. On dispatch
|
||||
# github.ref is `main`, so the tag ref is plumbed through explicitly: checkout
|
||||
# pins to inputs.ref, and the semver tags take inputs.version via `value=`.
|
||||
# auto-tag instead dispatches this workflow at the immutable tag and supplies
|
||||
# the bare version for metadata-action. The workflow rejects a dispatch whose
|
||||
# github.ref, checked-out HEAD, and relay-v tag do not resolve to one commit.
|
||||
# On the rescue path inputs.version is already bare (e.g. 0.3.0), so the
|
||||
# match=^relay-v(.*)$ regex simply no-ops (it warns, leaving the value
|
||||
# intact) and the bare version flows straight to the semver parser. On a
|
||||
# real push event value= is empty and the match strips relay-v from the ref.
|
||||
# Inputless canary dispatch (version="", ref=main) renders no semver tag.
|
||||
#
|
||||
# The :latest tag tracks the latest STABLE relay release: metadata-action's
|
||||
# `flavor.latest=auto` (its default) emits :latest only for non-prerelease
|
||||
@@ -63,11 +60,7 @@ on:
|
||||
inputs:
|
||||
version:
|
||||
description: "Semver version e.g. 0.3.0 (no relay-v prefix) — for relay-tag rescue dispatch"
|
||||
required: false
|
||||
ref:
|
||||
description: "Tag/branch/SHA to build, e.g. relay-v0.3.0"
|
||||
required: false
|
||||
default: main
|
||||
required: true
|
||||
|
||||
# One image build per ref; cancel superseded PR builds, but never cancel
|
||||
# tag/main builds (publishing must not be aborted mid-flight).
|
||||
@@ -112,11 +105,17 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
||||
with:
|
||||
# On workflow_dispatch (relay-tag rescue) build the tagged commit,
|
||||
# not main. Empty string = default ref for push/PR events.
|
||||
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || '' }}
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Verify tag-bound release source
|
||||
if: github.ref_type == 'tag' || github.event_name == 'workflow_dispatch'
|
||||
env:
|
||||
INPUT_VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
VERSION="${INPUT_VERSION:-${GITHUB_REF_NAME#relay-v}}"
|
||||
scripts/verify-release-ref.sh relay-v "$VERSION"
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
|
||||
with:
|
||||
@@ -327,8 +326,15 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
||||
with:
|
||||
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || '' }}
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- name: Verify tag-bound release source
|
||||
if: github.ref_type == 'tag' || github.event_name == 'workflow_dispatch'
|
||||
env:
|
||||
INPUT_VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
VERSION="${INPUT_VERSION:-${GITHUB_REF_NAME#relay-v}}"
|
||||
scripts/verify-release-ref.sh relay-v "$VERSION"
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
|
||||
with:
|
||||
|
||||
@@ -7,18 +7,12 @@ on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: "Semver version (e.g. 0.4.0) — only for manual runs"
|
||||
required: true
|
||||
ref:
|
||||
description: "Branch, tag, or SHA to build — only for manual runs"
|
||||
default: main
|
||||
description: "Semver version matching the v-prefixed dispatch tag"
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
# Shared setup: determine the version and create the release objects all four
|
||||
# platform jobs upload into. Runs on ubuntu with no OIDC so a branch
|
||||
# workflow_dispatch can exercise the platform builds without the codesign
|
||||
# AssumeRole wall that gates the arm64 job.
|
||||
# Shared setup: verify the immutable release tag, determine the version, and
|
||||
# create the release objects all four platform jobs upload into.
|
||||
setup:
|
||||
name: Setup
|
||||
if: github.repository == 'block/buzz'
|
||||
@@ -28,6 +22,7 @@ jobs:
|
||||
contents: write
|
||||
outputs:
|
||||
version: ${{ steps.version.outputs.version }}
|
||||
source_sha: ${{ steps.source.outputs.source_sha }}
|
||||
steps:
|
||||
- name: Determine version
|
||||
id: version
|
||||
@@ -51,13 +46,19 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Checkout at the same ref the platform jobs use so git rev-parse HEAD
|
||||
# pins the release to the right commit via --target.
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
||||
with:
|
||||
ref: ${{ github.event_name == 'push' && github.ref || inputs.ref }}
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Verify tag-bound release source
|
||||
id: source
|
||||
env:
|
||||
VERSION: ${{ steps.version.outputs.version }}
|
||||
run: |
|
||||
scripts/verify-release-ref.sh v "$VERSION"
|
||||
echo "source_sha=$(git rev-parse 'HEAD^{commit}')" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Create versioned GitHub release
|
||||
env:
|
||||
VERSION: ${{ steps.version.outputs.version }}
|
||||
@@ -108,9 +109,13 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
||||
with:
|
||||
ref: ${{ github.event_name == 'push' && github.ref || inputs.ref }}
|
||||
ref: ${{ needs.setup.outputs.source_sha }}
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Verify tag-bound release source
|
||||
run: scripts/verify-release-ref.sh v "$VERSION"
|
||||
|
||||
- uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1
|
||||
|
||||
- name: Install desktop dependencies
|
||||
@@ -291,7 +296,7 @@ jobs:
|
||||
run: gh release upload "v${VERSION}" "$DMG_PATH" --clobber
|
||||
|
||||
- name: Upload updater archive to rolling release
|
||||
if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.ref == format('v{0}', inputs.version))
|
||||
if: github.ref == format('refs/tags/v{0}', needs.setup.outputs.version)
|
||||
run: |
|
||||
gh release upload buzz-desktop-latest \
|
||||
"$ARCHIVE_PATH" \
|
||||
@@ -320,9 +325,13 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
||||
with:
|
||||
ref: ${{ github.event_name == 'push' && github.ref || inputs.ref }}
|
||||
ref: ${{ needs.setup.outputs.source_sha }}
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Verify tag-bound release source
|
||||
run: scripts/verify-release-ref.sh v "$VERSION"
|
||||
|
||||
- uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1
|
||||
|
||||
- name: Install desktop dependencies
|
||||
@@ -451,7 +460,7 @@ jobs:
|
||||
DMG_PATH: ${{ steps.unsigned.outputs.dmg }}
|
||||
|
||||
- name: Upload updater archive to rolling release
|
||||
if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.ref == format('v{0}', inputs.version))
|
||||
if: github.ref == format('refs/tags/v{0}', needs.setup.outputs.version)
|
||||
run: |
|
||||
gh release upload buzz-desktop-latest \
|
||||
"$ARCHIVE_PATH" \
|
||||
@@ -536,12 +545,18 @@ jobs:
|
||||
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
||||
with:
|
||||
ref: ${{ github.event_name == 'push' && github.ref || inputs.ref }}
|
||||
ref: ${{ needs.setup.outputs.source_sha }}
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Mark workspace safe for git (containerized job)
|
||||
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
||||
|
||||
- name: Verify tag-bound release source
|
||||
env:
|
||||
VERSION: ${{ needs.setup.outputs.version }}
|
||||
run: scripts/verify-release-ref.sh v "$VERSION"
|
||||
|
||||
- uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1
|
||||
|
||||
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
|
||||
@@ -687,7 +702,7 @@ jobs:
|
||||
--clobber
|
||||
|
||||
- name: Upload updater archive to rolling release
|
||||
if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.ref == format('v{0}', inputs.version))
|
||||
if: github.ref == format('refs/tags/v{0}', needs.setup.outputs.version)
|
||||
run: |
|
||||
gh release upload buzz-desktop-latest \
|
||||
"$ARCHIVE_PATH" \
|
||||
@@ -714,9 +729,14 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
||||
with:
|
||||
ref: ${{ github.event_name == 'push' && github.ref || inputs.ref }}
|
||||
ref: ${{ needs.setup.outputs.source_sha }}
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Verify tag-bound release source
|
||||
shell: bash
|
||||
run: scripts/verify-release-ref.sh v "$VERSION"
|
||||
|
||||
- uses: dtolnay/rust-toolchain@e081816240890017053eacbb1bdf337761dc5582 # 1.95.0
|
||||
with:
|
||||
targets: ${{ env.TARGET }}
|
||||
@@ -815,7 +835,7 @@ jobs:
|
||||
EXE_PATH: ${{ steps.artifacts.outputs.exe }}
|
||||
|
||||
- name: Upload updater archive to rolling release
|
||||
if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.ref == format('v{0}', inputs.version))
|
||||
if: github.ref == format('refs/tags/v{0}', needs.setup.outputs.version)
|
||||
shell: bash
|
||||
run: |
|
||||
gh release upload buzz-desktop-latest \
|
||||
@@ -829,12 +849,11 @@ jobs:
|
||||
|
||||
assemble-manifest:
|
||||
name: Assemble multi-platform latest.json
|
||||
# Only tag-backed releases assemble latest.json. Branch workflow_dispatch
|
||||
# runs skip rolling-release uploads, so their archive URLs never exist.
|
||||
# Only the tag-bound setup path can reach this job.
|
||||
if: |
|
||||
always() &&
|
||||
needs.setup.result == 'success' &&
|
||||
(github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.ref == format('v{0}', inputs.version)))
|
||||
github.ref == format('refs/tags/v{0}', needs.setup.outputs.version)
|
||||
runs-on: ubuntu-latest
|
||||
needs: [setup, release, release-macos-x64, release-linux, release-windows]
|
||||
timeout-minutes: 10
|
||||
@@ -846,9 +865,13 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
||||
with:
|
||||
ref: ${{ github.event_name == 'push' && github.ref || inputs.ref }}
|
||||
ref: ${{ needs.setup.outputs.source_sha }}
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Verify tag-bound release source
|
||||
run: scripts/verify-release-ref.sh v "$VERSION"
|
||||
|
||||
- name: Write signature files
|
||||
env:
|
||||
RESULT_ARM64: ${{ needs.release.result }}
|
||||
|
||||
Executable
+59
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
repo_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
verify="${repo_root}/scripts/verify-release-ref.sh"
|
||||
tmp=$(mktemp -d)
|
||||
trap 'rm -rf "$tmp"' EXIT
|
||||
|
||||
git -C "$tmp" init -q
|
||||
git -C "$tmp" config user.name test
|
||||
git -C "$tmp" config user.email test@example.com
|
||||
echo first >"$tmp/file"
|
||||
git -C "$tmp" add file
|
||||
git -C "$tmp" commit -qm first
|
||||
git -C "$tmp" tag v1.2.3
|
||||
|
||||
(
|
||||
cd "$tmp"
|
||||
GITHUB_REF=refs/tags/v1.2.3 "$verify" v 1.2.3
|
||||
)
|
||||
|
||||
if (
|
||||
cd "$tmp"
|
||||
GITHUB_REF=refs/heads/main "$verify" v 1.2.3
|
||||
); then
|
||||
echo "branch-backed desktop release was accepted" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo second >>"$tmp/file"
|
||||
git -C "$tmp" commit -qam second
|
||||
if (
|
||||
cd "$tmp"
|
||||
GITHUB_REF=refs/tags/v1.2.3 "$verify" v 1.2.3
|
||||
); then
|
||||
echo "release accepted HEAD after the tag commit" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
git -C "$tmp" tag relay-v2.0.0
|
||||
(
|
||||
cd "$tmp"
|
||||
GITHUB_REF=refs/tags/relay-v2.0.0 "$verify" relay-v 2.0.0
|
||||
)
|
||||
|
||||
if grep -q 'inputs\.ref' \
|
||||
"$repo_root/.github/workflows/release.yml" \
|
||||
"$repo_root/.github/workflows/docker.yml"; then
|
||||
echo "publisher workflow still accepts a caller-selected source ref" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
grep -q 'verify-release-ref\.sh' "$repo_root/.github/workflows/release.yml"
|
||||
grep -q 'verify-release-ref\.sh' "$repo_root/.github/workflows/docker.yml"
|
||||
grep -q 'test-release-ref-contract\.sh' "$repo_root/.github/workflows/ci.yml"
|
||||
grep -Fq -- "--ref \"\$TAG\"" \
|
||||
"$repo_root/.github/workflows/auto-tag-on-release-pr-merge.yml"
|
||||
|
||||
echo "release ref contract passed"
|
||||
Executable
+35
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if [ "$#" -ne 2 ]; then
|
||||
echo "usage: $0 <tag-prefix> <version>" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
tag_prefix=$1
|
||||
version=$2
|
||||
|
||||
if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]]; then
|
||||
echo "::error::Invalid release version '$version'" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
tag="${tag_prefix}${version}"
|
||||
expected_ref="refs/tags/${tag}"
|
||||
if [ "${GITHUB_REF:-}" != "$expected_ref" ]; then
|
||||
echo "::error::Release must run at ${expected_ref}; got ${GITHUB_REF:-<unset>}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
head_sha=$(git rev-parse 'HEAD^{commit}')
|
||||
if ! tag_sha=$(git rev-parse "refs/tags/${tag}^{commit}" 2>/dev/null); then
|
||||
echo "::error::Release tag '${tag}' is missing from the checkout" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$head_sha" != "$tag_sha" ]; then
|
||||
echo "::error::HEAD ${head_sha} does not match ${tag} commit ${tag_sha}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Verified ${expected_ref} at ${head_sha}"
|
||||
Reference in New Issue
Block a user