Skip to content

Commit d185295

Browse files
committed
Harden CLI release artifact publishing
1 parent cbd166f commit d185295

3 files changed

Lines changed: 85 additions & 5 deletions

File tree

.github/workflows/release.yml

Lines changed: 76 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -162,9 +162,34 @@ jobs:
162162
cp "$dir/linear-release.exe" "./release-files/linear-release-${platform}.exe"
163163
fi
164164
done
165+
166+
expected_assets=(
167+
linear-release-linux-x64
168+
linear-release-linux-arm64
169+
linear-release-darwin-x64
170+
linear-release-darwin-arm64
171+
)
172+
for asset in "${expected_assets[@]}"; do
173+
if [ ! -f "./release-files/$asset" ]; then
174+
echo "::error::Missing release asset: $asset"
175+
exit 1
176+
fi
177+
done
178+
179+
actual_asset_count=$(find ./release-files -maxdepth 1 -type f -name 'linear-release-*' | wc -l | tr -d ' ')
180+
if [ "$actual_asset_count" -ne "${#expected_assets[@]}" ]; then
181+
echo "::error::Expected ${#expected_assets[@]} release assets, found $actual_asset_count"
182+
exit 1
183+
fi
184+
185+
(
186+
cd ./release-files
187+
LC_ALL=C sha256sum linear-release-* > checksums.txt
188+
sha256sum --check --strict checksums.txt
189+
)
165190
ls -la ./release-files/
166191
167-
- name: Create Release
192+
- name: Create draft release
168193
uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2.6.2
169194
with:
170195
tag_name: ${{ steps.tag.outputs.tag_name }}
@@ -173,12 +198,60 @@ jobs:
173198
./release-files/*
174199
generate_release_notes: true
175200
token: ${{ secrets.GITHUB_TOKEN }}
176-
draft: false
201+
draft: true
177202
prerelease: false
178203

204+
- name: Publish immutable release
205+
env:
206+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
207+
TAG_NAME: ${{ steps.tag.outputs.tag_name }}
208+
run: gh release edit "$TAG_NAME" --draft=false
209+
210+
verify-release:
211+
name: Verify published release
212+
needs: release
213+
runs-on: ubuntu-latest
214+
permissions:
215+
contents: read
216+
217+
steps:
218+
- name: Verify immutable metadata and assets
219+
env:
220+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
221+
TAG_NAME: ${{ needs.release.outputs.tag_name }}
222+
run: |
223+
release=$(gh api "repos/${GITHUB_REPOSITORY}/releases/tags/${TAG_NAME}")
224+
if [ "$(jq -r '.immutable' <<<"$release")" != "true" ]; then
225+
echo "::error::Published release is not immutable"
226+
exit 1
227+
fi
228+
229+
for asset in \
230+
linear-release-linux-x64 \
231+
linear-release-linux-arm64 \
232+
linear-release-darwin-x64 \
233+
linear-release-darwin-arm64 \
234+
checksums.txt; do
235+
count=$(jq --arg asset "$asset" '[.assets[] | select(.name == $asset)] | length' <<<"$release")
236+
if [ "$count" -ne 1 ]; then
237+
echo "::error::Expected one published $asset asset, found $count"
238+
exit 1
239+
fi
240+
done
241+
242+
- name: Verify published checksums
243+
env:
244+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
245+
TAG_NAME: ${{ needs.release.outputs.tag_name }}
246+
run: |
247+
mkdir release-assets
248+
gh release download "$TAG_NAME" --dir release-assets
249+
cd release-assets
250+
sha256sum --check --strict checksums.txt
251+
179252
label-release:
180253
name: Label release with version
181-
needs: release
254+
needs: [release, verify-release]
182255
permissions:
183256
contents: read
184257
uses: ./.github/workflows/run-linear-release.yml

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ curl -L https://github.com/linear/linear-release/releases/latest/download/linear
5353
chmod +x linear-release
5454
```
5555

56+
Releases after `v0.16.0` include a `checksums.txt` asset containing the SHA-256 digest of every executable. These releases are immutable, so their tags, executables, and checksum manifests cannot be replaced after publication. CLI releases through `v0.16.0` predate this integrity contract.
57+
5658
## Quick Start
5759

5860
### GitHub Actions

RELEASING.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ This document describes how to create a new release of `linear-release`.
77
- You must be on the `main` branch with a clean working tree, up to date with `origin/main`
88
- The [GitHub CLI](https://cli.github.com) (`gh`) must be installed and authenticated
99
- `pnpm` must be installed
10+
- Immutable releases must be enabled in the repository settings
1011

1112
## Creating a release
1213

@@ -57,9 +58,13 @@ When a PR from a `release/*` branch is merged into `main`, the **Auto-tag releas
5758

5859
The **Release** workflow (`.github/workflows/release.yml`) is triggered by the new tag and:
5960

60-
1. Builds platform-specific executables (linux-x64, darwin-x64, darwin-arm64) using Bun
61+
1. Builds platform-specific executables (linux-x64, linux-arm64, darwin-x64, darwin-arm64) using Bun
6162
2. Code signs and notarizes the macOS binaries
62-
3. Creates a GitHub Release with the built binaries attached
63+
3. Generates and validates `checksums.txt` for the final executables
64+
4. Creates a draft GitHub Release and attaches all executables and the checksum manifest
65+
5. Publishes the completed draft as an immutable release
66+
67+
After publication, release assets cannot be changed or deleted and the associated tag cannot be moved. If a published artifact is incorrect, fix the issue and publish a new patch version instead of replacing the existing asset.
6368

6469
### 5. Update the GitHub action
6570

0 commit comments

Comments
 (0)