Problem
Release artifacts for macOS (x86_64-apple-darwin + aarch64-apple-darwin) and Windows (x86_64-pc-windows-msvc) ship unsigned. On user machines this triggers:
- macOS Gatekeeper —
"<binary>" cannot be opened because the developer cannot be verified. Users must right-click → Open or run xattr -d com.apple.quarantine.
- Windows SmartScreen —
Windows protected your PC blue dialog on first run. Users must click "More info → Run anyway".
This is the #1 friction point reported by non-technical users trying our binaries.
Why self-signed (not paid cert)
Apple Developer ID = $99/yr per org. Windows EV Code Signing = $300+/yr. Self-signed certs are a viable interim:
- macOS: a self-signed
Developer ID Application-style cert (security create-keychain + codesign --sign) signs the binary. Gatekeeper still shows a warning, but the binary is signature-bound — distinguishable from unsigned, immune to mid-flight tampering, and the warning text shifts from "developer cannot be verified" to a self-signing variant.
- Windows: a self-signed cert via
New-SelfSignedCertificate + signtool.exe sign /fd sha256 ... signs the binary. SmartScreen still warns on first run but the signature is checkable, and users who trust the publisher cert (one-click install into the local cert store) skip the warning thereafter.
Path forward when we can afford real certs: swap the cert source in CI, no other workflow change.
Scope
- Generate self-signed certs offline; store the cert + private key as GitHub Actions org secrets (
SELF_SIGNED_MAC_CERT_P12, SELF_SIGNED_MAC_CERT_PASS, SELF_SIGNED_WIN_CERT_PFX, SELF_SIGNED_WIN_CERT_PASS).
- macOS CI step — after the release binary builds, import the cert into a keychain (
security import + security set-key-partition-list) and codesign --force --options runtime --sign "<cert name>" <binary>. Verify with codesign -dv --verbose=4 <binary>. Re-tar / re-dmg after signing.
- Windows CI step — base64-decode the .pfx,
signtool sign /f cert.pfx /p $PASS /fd sha256 /tr http://timestamp.digicert.com /td sha256 <binary>.exe. Verify with signtool verify /pa <binary>.exe. Re-zip / re-msi after signing.
- Document the user-facing warning + bypass in README (or release-notes template) so users know what to expect.
Acceptance
- macOS arm64 + x86_64 release binaries pass
codesign -dv showing our cert as the signer
- Windows release binary passes
signtool verify /pa (allowing for the self-signed CA → expect "no trusted root" but valid signature chain)
- First-run UX captured in README: what dialog appears, how to bypass
Non-goals
- Notarization (requires Apple Developer membership)
- EV SmartScreen reputation (requires paid EV cert)
- Reproducible signing (out of scope; signing happens on CI runners)
Effort
M — straightforward Actions integration, ~50 lines of YAML per platform + cert-generation runbook + secret setup. Bulk of the work is testing the round-trip on a real CI run.
Problem
Release artifacts for macOS (
x86_64-apple-darwin+aarch64-apple-darwin) and Windows (x86_64-pc-windows-msvc) ship unsigned. On user machines this triggers:"<binary>" cannot be opened because the developer cannot be verified.Users must right-click → Open or runxattr -d com.apple.quarantine.Windows protected your PCblue dialog on first run. Users must click "More info → Run anyway".This is the #1 friction point reported by non-technical users trying our binaries.
Why self-signed (not paid cert)
Apple Developer ID = $99/yr per org. Windows EV Code Signing = $300+/yr. Self-signed certs are a viable interim:
Developer ID Application-style cert (security create-keychain+codesign --sign) signs the binary. Gatekeeper still shows a warning, but the binary is signature-bound — distinguishable from unsigned, immune to mid-flight tampering, and the warning text shifts from "developer cannot be verified" to a self-signing variant.New-SelfSignedCertificate+signtool.exe sign /fd sha256 ...signs the binary. SmartScreen still warns on first run but the signature is checkable, and users who trust the publisher cert (one-click install into the local cert store) skip the warning thereafter.Path forward when we can afford real certs: swap the cert source in CI, no other workflow change.
Scope
SELF_SIGNED_MAC_CERT_P12,SELF_SIGNED_MAC_CERT_PASS,SELF_SIGNED_WIN_CERT_PFX,SELF_SIGNED_WIN_CERT_PASS).security import+security set-key-partition-list) andcodesign --force --options runtime --sign "<cert name>" <binary>. Verify withcodesign -dv --verbose=4 <binary>. Re-tar / re-dmg after signing.signtool sign /f cert.pfx /p $PASS /fd sha256 /tr http://timestamp.digicert.com /td sha256 <binary>.exe. Verify withsigntool verify /pa <binary>.exe. Re-zip / re-msi after signing.Acceptance
codesign -dvshowing our cert as the signersigntool verify /pa(allowing for the self-signed CA → expect "no trusted root" but valid signature chain)Non-goals
Effort
M — straightforward Actions integration, ~50 lines of YAML per platform + cert-generation runbook + secret setup. Bulk of the work is testing the round-trip on a real CI run.