Sign the macOS thv binary with a Developer ID certificate - #6156
Draft
eleftherias wants to merge 2 commits into
Draft
Sign the macOS thv binary with a Developer ID certificate#6156eleftherias wants to merge 2 commits into
eleftherias wants to merge 2 commits into
Conversation
The macOS thv binary ships with only the ad-hoc signature Go's linker emits, so it carries no Team ID. Organizations that enforce binary allowlisting (Santa and similar) trust binaries by code-signing identity, leaving them to approve each release by CDHash or allow execution from a user-writable path. Both are fragile, so thv tends to get blocked in managed macOS environments. ToolHive Studio already signs with Developer ID Application: Stacklok, Inc, and the same certificate covers any binary from the team. Wire it into the CLI release. GoReleaser embeds anchore/quill, so signing happens on the existing Linux runner rather than requiring a macOS one. This is sign-only: omitting the notarize sub-block keeps Apple's notary service off the release critical path, and a Developer ID signature is all that Team ID allowlisting needs. Notarization can follow separately. Signing stays inert until the secrets are configured. Releases without them keep producing ad-hoc binaries and log a warning rather than failing, so this is safe to merge ahead of the credentials. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #6156 +/- ##
==========================================
+ Coverage 72.56% 72.62% +0.06%
==========================================
Files 736 736
Lines 76331 76331
==========================================
+ Hits 55391 55438 +47
+ Misses 17019 16956 -63
- Partials 3921 3937 +16 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
3 tasks
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.
Summary
The macOS
thvbinary ships with only the ad-hoc signature Go's linker emits, so it has no Developer ID and therefore no Team ID:Organizations that enforce binary allowlisting (Santa and similar) trust binaries by code-signing identity. Without a Team ID, their remaining options are approving each release by CDHash — which changes every build — or allowing execution from a user-writable install path. Both are operationally fragile, so
thvtends to get blocked outright in managed macOS environments.ToolHive Studio is already signed with
Developer ID Application: Stacklok, Inc (XMNPBXU9PV), and a Developer ID certificate signs any binary from the team rather than being scoped to one app. This wires the same credential into the CLI release.notarize.macospipe embeds anchore/quill as a library, so no macOS runner is added and release minutes don't change.notarizesub-block keeps Apple's notary service off the release critical path. A Developer ID signature is all Team ID allowlisting needs; notarization can land separately.MACOS_SIGN_ENABLEDflag gates the pipe. Without the secrets, releases produce ad-hoc binaries exactly as they do today and emit a warning annotation instead of failing.Fixes #5862
Type of change
Test plan
task test)task test-e2e)task lint-fix)No Go code changes, so
task testandtask lint-fixdon't apply. Verified with GoReleaser directly:goreleaser checkagainst the real.goreleaser.yamlreports valid. The singlebrewsdeprecation it flags reproduces identically onHEAD, so it predates this PR.MACOS_SIGN_ENABLED=falseand the certificate variables entirely unset:release succeeded. Theenabledgate short-circuits before{{ .Env.APPLE_CERTIFICATE }}is templated, so there's no missing-key error. This also proves the templated stringfalseparses as boolean false; had it been truthy, the same run would have failed on an empty certificate..p12: GoReleaser reachedsign & notarize macOS binariesand quill failed withunable to find remaining chain certificates. Base64 decode, PKCS#12 parse, and the password all worked; it got as far as chain validation. Confirms an expired or wrong certificate aborts the release rather than silently shipping ad-hoc binaries.id: toolhiveis a no-op — with this repo'sproject_nameandbinary, the resolved build ID fromartifacts.jsonistoolhiveboth with and without the explicitid, at the same pathdist/toolhive_darwin_arm64_v8.0/thv. The implicit default was alreadytoolhive.runblock passesbash -n.Items 2–4 ran against minimal projects replicating the relevant config, because a full six-target ToolHive build exceeded the sandbox timeout. Item 1 ran against the real config.
Does this introduce a user-facing change?
Not on merge — no behavior changes until the signing secrets are configured. Once they are, macOS
thvbinaries in release archives carry aDeveloper ID Application: Stacklok, Incsignature and a Team ID that can be allowlisted by code-signing identity. Homebrew installs benefit automatically, since the signature is embedded in the Mach-O and survives archive extraction.Special notes for reviewers
Two secrets are required before this does anything:
APPLE_CERTIFICATE(base64.p12) andAPPLE_CERTIFICATE_PASSWORD. Studio'sKEYCHAIN_PASSWORDis not needed — quill reads the.p12directly with no keychain involved. Since both repos need the same values, org-level secrets are preferable to duplicating them. Recommend validating on a prerelease tag before a stable release.Why a
MACOS_SIGN_ENABLEDflag instead ofisEnvSet "APPLE_CERTIFICATE". GoReleaser's own docs recommend theisEnvSetidiom, so this looks like a deviation worth fixing — please don't. An absent GitHub secret still sets the environment variable, to the empty string, soisEnvSetwould report "configured" and fail the release with an empty certificate. The flag is computed from whether the secret is non-empty.The section named
notarizeperforms signing. That's GoReleaser's name for the whole feature; sign-only is a mode of it, reached by omitting the innernotarizeblock. Confusing, but correct.Notarization is a deliberate follow-up, needing three more secrets (
APPLE_API_KEY,APPLE_KEY_ID,APPLE_ISSUER_ID). Worth knowing it wouldn't fully close the Gatekeeper story either way: a notarization ticket cannot be stapled to a bare Mach-O binary, only to bundles and installers, so Gatekeeper would still rely on an online check for a standalone CLI. Full offline Gatekeeper behavior would require shipping a signed.pkg, which is materially larger work.No verification job. An earlier revision added a
macos-latestjob assertingcodesign --verifyon the published artifacts. Dropped: a bad certificate already fails the release (see test 3), a missing secret already warns, the check could only run post-publish so it would alert rather than gate, and it was the only thing forcing a macOS runner into an otherwise all-Linux pipeline. The first release should be spot-checked by hand instead.Also worth noting: there's currently no CI validation of
.goreleaser.yamlat all, so a config typo only surfaces at release time. Agoreleaser checkstep on PRs would be cheap insurance — happy to open that separately.Generated with Claude Code