Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions .github/workflows/mirror-release.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
name: Mirror signed release

on:
release:
types: [published]
workflow_dispatch:
inputs:
tag:
Expand Down Expand Up @@ -31,7 +29,7 @@ jobs:
LC_ALL: C
GH_HOST: github.com
GH_PROMPT_DISABLED: '1'
TAG: ${{ github.event.release.tag_name || inputs.tag }}
TAG: ${{ inputs.tag }}
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
TRUSTED_WORKFLOW_SHA: ${{ github.workflow_sha }}
MIRROR_BASE_URL: https://dl.ll.cd/linux-temp-admin
Expand All @@ -46,9 +44,12 @@ jobs:
run: |
set -Eeuo pipefail
[[ "$GITHUB_REPOSITORY" == xxvcc/linux-temp-admin ]]
[[ "$GITHUB_EVENT_NAME" == workflow_dispatch ]]
[[ "$DEFAULT_BRANCH" == main ]]
[[ "$GITHUB_REF" == "refs/heads/$DEFAULT_BRANCH" ]]
[[ "$GITHUB_WORKFLOW_REF" == "$GITHUB_REPOSITORY/.github/workflows/mirror-release.yml@refs/heads/$DEFAULT_BRANCH" ]]
[[ "$TRUSTED_WORKFLOW_SHA" =~ ^[0-9a-f]{40}$ ]]
[[ "$GITHUB_SHA" == "$TRUSTED_WORKFLOW_SHA" ]]
[[ "$MIRROR_ENVIRONMENT_CONFIGURED" == true ]]
[[ "$TAG" =~ ^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-([0-9A-Za-z]+([.-][0-9A-Za-z]+)*))?$ ]]
(( ${#BASH_REMATCH[1]} > 1 || 10#${BASH_REMATCH[1]} >= 2 ))
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

All notable changes to this project are documented here.

## Unreleased

- Run mirror synchronization only from an explicit protected-`main` dispatch.
GitHub binds a `release` event workflow to the released tag, so the automatic
trigger could not satisfy the default-branch trust check and must not receive
the mirror deployment credential.

## v2.8.3 - 2026-07-27

- Run the release artifact handoff on the official actions' native Node.js 24
Expand Down
19 changes: 16 additions & 3 deletions docs/releasing.md
Original file line number Diff line number Diff line change
Expand Up @@ -744,7 +744,20 @@ timeout and uses a private, transient configuration directory.

Publishing the GitHub Release is not the end of the release. The
[`Mirror signed release`](../.github/workflows/mirror-release.yml) workflow must
finish before announcement. It accepts only an immutable public GitHub Release
be explicitly dispatched from the protected default branch and finish before
announcement:

```bash
gh workflow run mirror-release.yml --repo xxvcc/linux-temp-admin \
--ref main -f tag=v2.8.3
gh run list --repo xxvcc/linux-temp-admin \
--workflow mirror-release.yml --event workflow_dispatch --limit 1
```

The explicit default-branch dispatch is a credential boundary: a GitHub
`release` event runs with the released tag as its workflow ref, so it must not
receive the `release-mirror` Environment credential. The workflow accepts only
an immutable public GitHub Release
with the exact five-asset release set, rechecks the checksum manifest and both
ed25519 signatures against the trusted keyring, verifies the released binaries,
and copies one complete release into
Expand All @@ -758,8 +771,8 @@ verifies the stable installer hash and fails if either client reports that it
used the GitHub fallback.

Create a protected GitHub Environment named `release-mirror` with no required
reviewers. Disable administrator bypass, and allow only protected `v*` tags plus
the protected default branch used for an explicit recovery dispatch. Enable
reviewers. Disable administrator bypass, and allow only the protected default
branch used for normal publication and recovery dispatches. Enable
immutable Releases for the repository; synchronization fails closed when the
selected GitHub Release is mutable. Configure exactly these environment values:

Expand Down
8 changes: 7 additions & 1 deletion internal/selfmanage/release_pipeline_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,12 +112,16 @@ func TestMirrorReleaseWorkflowPublishesVerifiedImmutableContentFailClosed(t *tes
mirror := readReleaseFile(t, "../../.github/workflows/mirror-release.yml")
installer := readReleaseFile(t, "../../scripts/install.sh")
for _, required := range []string{
"types: [published]",
"workflow_dispatch:",
"group: linux-temp-admin-release-mirror-stable",
"cancel-in-progress: false",
"environment: release-mirror",
"LTA_RELEASE_MIRROR_ENVIRONMENT_CONFIGURED",
`TAG: ${{ inputs.tag }}`,
`[[ "$GITHUB_EVENT_NAME" == workflow_dispatch ]]`,
`[[ "$GITHUB_REF" == "refs/heads/$DEFAULT_BRANCH" ]]`,
`$GITHUB_REPOSITORY/.github/workflows/mirror-release.yml@refs/heads/$DEFAULT_BRANCH`,
`[[ "$GITHUB_SHA" == "$TRUSTED_WORKFLOW_SHA" ]]`,
"GH_HOST: github.com",
"GH_PROMPT_DISABLED: '1'",
"timeout -k 5 60 gh api",
Expand Down Expand Up @@ -149,6 +153,8 @@ func TestMirrorReleaseWorkflowPublishesVerifiedImmutableContentFailClosed(t *tes
}
}
for _, prohibited := range []string{
"\n release:",
"github.event.release.tag_name",
"types: [published, edited]",
"contents: write",
"--clobber",
Expand Down