Skip to content

ci(release): sign macOS and Windows binaries#5

Merged
bradfair merged 2 commits into
mainfrom
feat/release-signing
Apr 21, 2026
Merged

ci(release): sign macOS and Windows binaries#5
bradfair merged 2 commits into
mainfrom
feat/release-signing

Conversation

@bradfair

@bradfair bradfair commented Apr 21, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Wire macOS codesign + notarize and Windows Azure Trusted Signing into the release pipeline.
  • Release runner moves to macos-14 so codesign and xcrun notarytool are available; linux/windows binaries still cross-compile via CGO_ENABLED=0.
  • Goreleaser runs with --skip=publish; notarization runs against dist/*darwin*.tar.gz; a follow-up gh release upload attaches signed artifacts to the release release-please already created.

Secrets consumed

  • APPLE_DISTRIBUTION_PKCS12, APPLE_DISTRIBUTION_PKCS12_PASSPHRASE — keychain import
  • APPLE_ID, APPLE_APP_SPECIFIC_PASSWORD, APPLE_TEAM_ID — notarytool
  • AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_CLIENT_SECRETsign CLI

Same secrets already used by viewas-app/elevate-app.

Test plan

  • Tag a pre-release to exercise the pipeline end-to-end
  • Verify downloaded macOS archive passes spctl -a -vv after tar xzf
  • Verify Windows .exe has a valid Authenticode sig via signtool verify /pa

Summary by CodeRabbit

  • Chores
    • Binaries are now code-signed for macOS and Windows, improving authenticity and trust.
    • macOS archives are notarized to meet platform security requirements.
    • Release pipeline updated to perform signing, notarization, and upload of built artifacts during releases.

Darwin: codesign with Developer ID Application, notarize archives via
xcrun notarytool after GoReleaser builds but before upload. Keychain is
created per-run from APPLE_DISTRIBUTION_PKCS12 and discarded with the
runner.

Windows: Azure Trusted Signing via Microsoft's `sign` dotnet tool under
the shared HPT "Elevate" profile.

Linux: intentionally unsigned — distros don't consume Authenticode-style
sigs; the sha256 in checksums.txt is the integrity anchor.

Release workflow moves to macos-14 so codesign and notarytool are
available; linux/windows binaries still cross-compile via CGO_ENABLED=0.
goreleaser runs with --skip=publish so archives exist on disk for the
notarize step; a follow-up gh release upload attaches the signed,
notarized artifacts to the release release-please already created.
@coderabbitai

coderabbitai Bot commented Apr 21, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds platform-specific binary signing and macOS archive notarization to the release pipeline, integrates signing as a GoReleaser post-build hook, and updates the GitHub Actions release workflow to run on macOS with steps for certificate import, Windows signing tooling, notarization, and manual artifact upload.

Changes

Cohort / File(s) Summary
Signing & Notarization Scripts
.github/scripts/sign-binary.sh, .github/scripts/notarize-archive.sh
New Bash scripts: sign-binary.sh applies platform-specific signing (macOS codesign, Windows sign tool, no-op on Linux); notarize-archive.sh submits macOS .tar.gz archives to Apple notarization via xcrun notarytool.
GitHub Actions Release Workflow
.github/workflows/release.yml
Workflow runner changed to macos-14; adds steps to import Apple Developer ID into a temp keychain, install .NET and the sign CLI for Windows signing, notarize darwin archives by invoking the new script, and replace GoReleaser publish with explicit gh release upload from dist/. Also sets env vars for signing tools and runs goreleaser release --clean --skip=publish.
GoReleaser Hook
.goreleaser.yml
Added a builds hooks.post entry for ana that runs .github/scripts/sign-binary.sh with {{ .Path }} and {{ .Os }}, enabling post-build signing before archiving.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor GH_Actions as "GitHub Actions (release)"
  participant GoReleaser as "GoReleaser"
  participant SignHook as ".github/scripts/sign-binary.sh"
  participant macOS_Signer as "codesign (macOS)"
  participant Win_Signer as "sign (Azure Trusted Signing)"
  participant Archiver as "GoReleaser archiver"
  participant Notarizer as ".github/scripts/notarize-archive.sh"
  participant Apple_Notary as "xcrun notarytool"
  participant GH_Release as "gh release (upload)"

  GH_Actions->>GoReleaser: run goreleaser (--skip=publish)
  GoReleaser->>SignHook: invoke post-build hook ({{ .Path }}, {{ .Os }})
  SignHook-->>macOS_Signer: codesign (darwin) / verify
  SignHook-->>Win_Signer: sign CLI (windows) / verify
  SignHook-->>SignHook: no-op (linux)
  SignHook-->>GoReleaser: return signed artifact
  GoReleaser->>Archiver: create archives in dist/
  GH_Actions->>Notarizer: loop dist/*darwin*.tar.gz -> run notarize-archive.sh
  Notarizer->>Apple_Notary: xcrun notarytool submit --wait
  Apple_Notary-->>Notarizer: notarization result
  GH_Actions->>GH_Release: gh release upload dist/* --clobber
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰 Hooray, the build hops clear and bright,

Codesigned and notarized, safe in flight.
Keys and tools dance in nightly delight,
Dist blooms with artifacts, all snug and right. 🥕✨


Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai 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.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/scripts/notarize-archive.sh:
- Around line 10-16: Validate the archive input before calling xcrun: check that
the positional parameter assigned to the variable archive is non-empty and that
the file exists/readable, and if not emit a clear error to stderr (including the
provided archive value or lack thereof) and exit with a non-zero status; update
the top of the script near the archive="$1" binding (and before the xcrun
notarytool submit invocation) to perform these checks so the call to xcrun
notarytool submit --apple-id "$APPLE_ID" --password
"$APPLE_APP_SPECIFIC_PASSWORD" --team-id "$APPLE_TEAM_ID" --wait only runs when
archive is valid.

In @.github/scripts/sign-binary.sh:
- Line 14: The script assigns arch with "path=\"$1\" os=\"$2\" arch=\"$3\"" but
never uses arch; either remove the arch assignment (change to "path=\"$1\"
os=\"$2\"" and update .goreleaser.yml to stop passing {{ .Arch }}) or
intentionally use it (e.g., include arch in logging/debug output or add a clear
comment explaining it's reserved for future use) — edit sign-binary.sh to
implement one of these fixes and adjust any callers (like .goreleaser.yml) if
you remove the parameter.

In @.github/workflows/release.yml:
- Line 56: The unquoted command substitution in the security command can cause
word-splitting for keychain paths with spaces; update the invocation of security
in the workflow so the substitution is quoted (i.e., change the arguments to use
"$(security list-keychains -d user | tr -d '"')" or an equivalent that preserves
spaces) when calling security list-keychains -d user -s "$KEYCHAIN" $(security
list-keychains -d user | tr -d '"'); ensure you preserve the existing tr -d '"'
processing but wrap the substitution in double quotes to prevent splitting.
- Around line 79-88: Remove APPLE_ID, APPLE_APP_SPECIFIC_PASSWORD, and
APPLE_TEAM_ID from the env block passed to the GoReleaser step since
sign-binary.sh (invoked by the GoReleaser job) doesn't use them for codesign;
instead, set those three Apple-related secrets only in the step that runs
notarize-archive.sh (or the notarization job) and update the inline comment
(currently "Apple — consumed by sign-binary.sh") to accurately state they are
used by notarize-archive.sh/notarization, leaving codesign to rely on the
keychain identity.
- Around line 112-118: The artifacts array may be empty (with nullglob on)
causing gh release upload to fail; modify the script around the artifacts
declaration and the gh release upload call to first check the array length
(e.g., if [ ${`#artifacts`[@]} -gt 0 ]; then ... ) and only invoke gh release
upload "$GITHUB_REF_NAME" "${artifacts[@]}" --clobber when there are artifacts,
otherwise skip or log a message; reference the artifacts array and the gh
release upload invocation when making this change.
- Around line 67-68: The workflow step "Install sign CLI" currently installs a
prerelease pinned version via the command `dotnet tool install --global sign
--prerelease --version "0.9.1-beta.26179.1"`, which is risky for production;
either change this step to install a stable, non-prerelease `sign` version
(remove `--prerelease` and pin a released version) or add an inline comment and
a documented justification in the repo explaining exactly why this prerelease is
required (including link to the issue/PR or feature that necessitates it) and
add a TODO to revert to stable once a release is available so reviewers can
locate and validate the rationale.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Lite

Run ID: 8f3217d5-9626-41bd-9cff-6e18f3543b05

📥 Commits

Reviewing files that changed from the base of the PR and between 9ee3e3d and 30bee47.

📒 Files selected for processing (4)
  • .github/scripts/notarize-archive.sh
  • .github/scripts/sign-binary.sh
  • .github/workflows/release.yml
  • .goreleaser.yml

Comment thread .github/scripts/notarize-archive.sh
Comment thread .github/scripts/sign-binary.sh Outdated
Comment thread .github/workflows/release.yml Outdated
Comment thread .github/workflows/release.yml
Comment thread .github/workflows/release.yml
Comment thread .github/workflows/release.yml
- Quote `security list-keychains` subshell to survive spaces in paths.
- Drop APPLE_* from GoReleaser step env; codesign resolves identity from
  the imported keychain, not env vars. They stay on the notarize step.
- Drop unused `arch` parameter from sign-binary.sh; prune {{ .Arch }}
  from the hook template.
- Guard `gh release upload` against empty artifact glob with explicit
  error rather than a confusing gh error.
- Document why `sign` is pinned to a beta (no stable tag ships).
@bradfair bradfair enabled auto-merge April 21, 2026 03:47

@coderabbitai coderabbitai 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.github/workflows/release.yml (1)

25-27: ⚠️ Potential issue | 🟡 Minor

Align Go version with project configuration.

The workflow uses go-version: "1.25", but the project's go.mod and Dockerfile specify Go 1.23. Ensure the release workflow version matches the project's configured Go version.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/release.yml around lines 25 - 27, Update the release
workflow to use the project's configured Go version: change the actions/setup-go
step (uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff) so the
go-version input matches Go 1.23 instead of "1.25" (update the go-version:
"1.25" value to "1.23"), ensuring consistency with go.mod and the Dockerfile.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In @.github/workflows/release.yml:
- Around line 25-27: Update the release workflow to use the project's configured
Go version: change the actions/setup-go step (uses:
actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff) so the go-version
input matches Go 1.23 instead of "1.25" (update the go-version: "1.25" value to
"1.23"), ensuring consistency with go.mod and the Dockerfile.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Lite

Run ID: a77b53b1-6942-4ace-ab70-6013f7abcc44

📥 Commits

Reviewing files that changed from the base of the PR and between 30bee47 and 5e4811f.

📒 Files selected for processing (3)
  • .github/scripts/sign-binary.sh
  • .github/workflows/release.yml
  • .goreleaser.yml

@bradfair bradfair added this pull request to the merge queue Apr 21, 2026
Merged via the queue into main with commit 2618012 Apr 21, 2026
10 checks passed
@bradfair bradfair deleted the feat/release-signing branch April 21, 2026 03:50
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.

1 participant