ci: sign and notarize releases with Developer ID - #17
Merged
Conversation
added 3 commits
August 2, 2026 18:24
Signing moves from a hand-rolled 'codesign --deep' to xcodebuild archive and export. The app is sandboxed and its entitlements reference $(PRODUCT_BUNDLE_IDENTIFIER) for Sparkle's installer XPC mach-lookup exceptions. codesign does not expand build settings, so the old command signed a literal '$(PRODUCT_BUNDLE_IDENTIFIER)-spks' into the shipped app. xcodebuild expands it and signs Sparkle.framework, Updater.app, Installer.xpc and Downloader.xpc inside-out with their own entitlements. Releases now fail loudly when signing or notary credentials are missing, instead of falling back to ad-hoc signing and leaving users to clear quarantine by hand. Signing secrets are scoped to a 'release' environment that only v* tags can read. scripts/build-release.sh holds the build so local and CI runs produce the same artifact. Repository is now TerrifiedBug/tickerbar and the asset is tickerbar.zip.
The release job imports the Developer ID private key, so a mutable action tag is a live exfiltration path: whoever controls the tag controls code running in the same job as the key. Pin every action to a full commit SHA and let Dependabot keep the pins current.
No released build has ever been sandboxed. The old release workflow only reached its '--deep --entitlements' path when signing secrets were present, and they never were, so every shipped build came from the ad-hoc fallback 'codesign --force --deep --sign -', which applies no entitlements at all. Signing correctly would therefore have enabled the sandbox for the first time. That relocates preferences from ~/Library/Preferences into ~/Library/Containers, so every existing user would have opened v1.5.0 to an empty watchlist with their holdings and alerts gone. TickerBar ships through Developer ID for Homebrew, never the App Store, so the sandbox is optional. Remove the entitlements file, its build setting and the sandbox-only SUEnableInstallerLauncherService key. Hardened runtime stays on, which is what notarization actually requires. Verified: the notarized build reports no entitlements, creates no container, and reads and writes the existing unsandboxed preferences.
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.
Releases are now signed with the Developer ID Application certificate and notarized by Apple, so Homebrew and manual installs open without a Gatekeeper prompt.
Signing
Signing moves from a hand-rolled
codesign --deeptoxcodebuild archive+-exportArchive. This is not cosmetic. TickerBar is sandboxed and its entitlements reference$(PRODUCT_BUNDLE_IDENTIFIER)for Sparkle's installer XPC mach-lookup exceptions.codesigndoes not expand build settings, so the old command baked the literal string$(PRODUCT_BUNDLE_IDENTIFIER)-spksinto the shipped app instead ofcom.tickerbar.app-spks.xcodebuildexpands it, and signsSparkle.framework,Updater.app,Installer.xpcandDownloader.xpcinside-out with their own entitlements rather than stamping the main app's entitlements onto every nested binary.Verified locally against a real Developer ID build: every nested binary carries
flags=0x10000(runtime), a secure timestamp, and the Developer ID authority, and the main app's entitlements now resolve tocom.tickerbar.app-spks/com.tickerbar.app-spki.No more silent ad-hoc fallback
The old workflow fell back to ad-hoc signing when secrets were absent, which is how shipped builds ended up needing
xattr -dr com.apple.quarantine. The release now fails loudly if signing or notary credentials are missing, and assertsspctl --assessreportssource=Notarized Developer IDbefore publishing.Secret scoping
Signing secrets live in a
releaseGitHub environment restricted tov*tags, so no other workflow or ref can read the Developer ID key.Shared build script
scripts/build-release.shholds build, sign, notarize, staple and package, and CI calls it, so a local run and a CI run produce the same artifact.SKIP_NOTARIZE=1allows local testing.Rename
Repository is now
TerrifiedBug/tickerbarand the release asset istickerbar.zip. GitHub redirects the old paths, so existing installs keep resolving their appcast and updating. TheTickerBar.appbundle name is unchanged.Verification
xcodebuild test)scripts/build-release.shcompletes end to end withSKIP_NOTARIZE=1codesign --verify --deep --strictpasses on the exported app