Darwin release binaries (darwin_amd64 + darwin_arm64) are signed with an Apple Developer ID Application certificate and notarized by Apple before publication. Users installing via brew install gitsocial-org/tap/gitsocial or downloading a release archive get a binary that launches without a Gatekeeper prompt and without needing xattr -d com.apple.quarantine.
| Stage | Where | What runs |
|---|---|---|
| Sign the binary | .goreleaser.yaml → builds.darwin.hooks.post | rcodesign sign --p12-file=/tmp/cert.p12 --p12-password=… --code-signature-flags=runtime <binary> runs during the build phase, before archiving, so the .zip wraps a signed binary |
| Archive | .goreleaser.yaml → archives.darwin | Each signed binary is wrapped in .zip (Apple's notary won't accept .tar.gz) |
| Publish | goreleaser release | Uploads archives + SBOMs + checksums to the GitHub release for the tag |
| Notarize | scripts/release.sh step 3 (build) | rcodesign notary-submit --wait runs against each dist/*_darwin_*.zip after goreleaser publishes. Apple registers the cdhash centrally; the archive itself is unchanged |
Signing uses rcodesign — a Linux-compatible signer/notarizer installed on the release machine (v0.29.x tested; the release driver only checks it is present on PATH). Because rcodesign needs no Apple tooling, signing and notarization run on the release machine regardless of its OS; no macOS host is required.
The release driver (scripts/release.sh) reads these from the environment on the release machine (full credential set documented in the driver header):
| Secret | Value |
|---|---|
APPLE_CERT_P12 | base64 of the Developer ID Application .p12 (base64 -i cert.p12) |
APPLE_CERT_PASSWORD | password set when exporting the .p12 from Keychain |
APPLE_API_KEY_P8 | full contents of the App Store Connect .p8 (PEM, headers and all) |
APPLE_API_KEY_ID | Key ID (10 alphanumeric chars, shown in App Store Connect → Users and Access → Integrations → Team Keys) |
APPLE_API_ISSUER_ID | Issuer UUID (shown on the same page) |
Apple Developer ID Application certificates expire after one year. Renewal:
.p12, set a password.APPLE_CERT_P12 (base64) and APPLE_CERT_PASSWORD.codesign -dv on a downloaded archive shows the new certificate's serial number.The .p8 API key never expires, but rotate after any suspected exposure (e.g. accidental paste into a log, ticket, or chat).
.p8 (one-time download).APPLE_API_KEY_P8, APPLE_API_KEY_ID, APPLE_API_ISSUER_ID.Anyone — user, fork operator, auditor — can confirm a release archive's signature and notarization status from a Mac:
# primary: the gitsocial.org bucket
curl -fsSLO https://gitsocial.org/artifacts/<X.Y.Z>/gitsocial_<X.Y.Z>_darwin_arm64.zip
# mirror: GitHub Releases
# curl -fsSLO https://github.com/gitsocial-org/gitsocial/releases/download/v<X.Y.Z>/gitsocial_<X.Y.Z>_darwin_arm64.zip
unzip gitsocial_<X.Y.Z>_darwin_arm64.zip
# signature check (what Gatekeeper runs)
codesign --verify --deep --strict --verbose=2 ./gitsocial
codesign -dv --verbose ./gitsocial 2>&1 | grep Authority
Expected:
valid on disk + satisfies its Designated RequirementAuthority=… lines: Developer ID Application: Mukhsimjon Rakhimov (J33FZDK3T7), Developer ID Certification Authority, Apple Root CAFor notarization status, run spctl --assess --type install --verbose ./gitsocial.zip against the .zip (not the binary — spctl -t exec misreports CLI binaries as "not an app").
signs: / binary_signs:The natural goreleaser pattern for code signing is one of those blocks with signature: "${artifact}" to indicate in-place signing. Don't reach for it here. With that pattern, goreleaser registers the artifact a second time as a signature pointing at the same path; the GitHub release upload then POSTs each darwin archive twice and the second attempt fails with 422 already_exists. The release fails at the publish step even though signing itself succeeded. The current config dodges this by signing in builds.darwin.hooks.post (no artifact-list registration) and doing notarization as a separate post-goreleaser step in the release driver (scripts/release.sh step 3).
Apple notary returns Invalid
Open the notary log URL printed by rcodesign notary-submit. Common causes:
--code-signature-flags=runtime to the build hookThe release succeeds but brew install still warns about an unverified developer
The cask in homebrew-tap may be pointing at an older release. Check the cask: gh api repos/gitsocial-org/homebrew-tap/contents/Casks/gitsocial.rb --jq '.content' | base64 -d | grep version. If goreleaser didn't update it, the GORELEASER_HOMEBREW_CASK_GITHUB_TOKEN secret may have insufficient scope (needs repo write on homebrew-tap).
brew install says "Refusing to load cask from untrusted tap"
Homebrew 5.0+ requires explicit trust for casks in third-party taps. Users hit this on first install. The README documents the one-time brew trust gitsocial-org/tap step. This isn't fixable by changing our signing — it's a Homebrew client-side policy. Switching to a Formula (brews: in goreleaser) would avoid the prompt but brews: is deprecated since goreleaser v2.10 and slated for removal; current trade-off favors keeping homebrew_casks: and documenting the trust step.