feat(ci): sign and notarize the macOS binary, verify it on macOS - #134
Merged
Conversation
The darwin binary is cross-compiled from Linux and was ad-hoc signed, never built or run on macOS before shipping. Two consequences: a binary that cannot launch reaches users (the class of bug patch-macos-sdk.py exists for, and that breaks `brew install` outright since the formula runs `pond completions` at install time), and the signature carries no publisher identity. Sign with a CI-dedicated Developer ID Application certificate plus hardened runtime in build-dist, then gate the publish on a macos-15 job that launches the artifact, asserts the signature, and notarizes it. - split publish-release into dist-build -> macos-verify -> publish-release; needs: does the sequencing, publish rehydrates dist/ from an artifact - sign via rcodesign --p12-file when APPLE_P12_BASE64 is set, ad-hoc otherwise so local and fork builds still produce a loadable arm64 binary - notarize with xcrun notarytool on the Mac itself; gate on the JSON status, since --wait exits 0 even when the notary returns Invalid - assert Developer ID authority + runtime flag rather than spctl, which rejects every bare CLI binary regardless of notarization - drop the formula's on_intel block: it served the aarch64 tarball behind a depends_on arch: :arm64 that already refuses Intel - bump actions/checkout to v7.0.1, release-plz/action to v0.5.131
It proved dist-build's signing path and macos-verify against Apple on #134. Left in place it would rebuild and re-notarize on every future PR.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
The darwin binary is cross-compiled from Linux (
cargo zigbuild) and was ad-hoc signed. It is never built or run on macOS before shipping, so two things could reach users unnoticed:ops/scripts/patch-macos-sdk.pyexists for, and it does not degrade gracefully: the Homebrew formula callsgenerate_completions_from_executableat install time, so a dead binary breaksbrew installitself..app, or.pkg.What
publish-releaseis split so the macOS checks gate the publish without re-running the four-target build;needs:does all the sequencing andpublishrehydratesdist/from an artifact.Signing and notarization ship together deliberately. Gatekeeper has an explicit
[Unnotarized Developer ID] P0 deny executerule, so a Developer ID signature without notarization is strictly worse than ad-hoc.Notes on two non-obvious choices
spctlis not used to verify. It rejects every bare CLI binary regardless of notarization — verified against a known-good notarized binary:So
macos-verifyasserts the two properties notarization actually requires (Developer ID authority, hardened runtime flag) instead.The notarization gate is the JSON status, not the exit code.
notarytool submit --waitexits 0 even when the notary returnsInvalid.Credentials
A CI-dedicated Developer ID Application certificate, separate from the interactive one, so it is independently revocable. Its private key is not in any local keychain. Notarization uses an App Store Connect API key (
notarytoolsupports no other auth that can be exported).Verified before this branch existed
The full chain was run locally against Apple with these exact commands and credentials:
rcodesignsign via themoon.ymlcode pathcodesignassertionsnotarytool submit --wait{"status":"Accepted"}Before merging
.github/workflows/dry-run-apple-signing.ymlis temporary and must be deleted. It mirrorsdist-build+macos-verifyonpull_request(the production jobs only fire on a release push), giving this PR a real signed and notarized rehearsal.It does not exercise
publish-release, whose changes are mechanical: artifact download instead of in-job build,Vfrom a job output.