fix(release): harden the sign/notarize/appcast pipeline for the first real run#8
Merged
Merged
Conversation
… real run A pre-flight audit of the never-yet-run signed pipeline found three defects that would fail a real `v0.1.0` release *after* notarization (the worst place to fail), plus a hazard that could silently ship an unsigned update: - generate_appcast was undiscoverable on a clean CI runner (only .build/ artifacts was searched). Broaden discovery to the DerivedData that `xcodebuild archive` populates, and warm .build via `swift package resolve`. - package.sh emits Capsule-<v>.zip AND .dmg into dist/, and generate_appcast rejects same-version duplicates. Sign the appcast in an isolated dir holding only the update zip; the dmg is a human download Sparkle never sees. - The Sparkle key secret's base64 encoding was ambiguous; release.yml now accepts either the raw key string or a double-encoded secret. - appcast.sh now FAILS if any enclosure lacks sparkle:edSignature, so a key mismatch can't silently publish an update every client rejects. Signing hardening: pin `signingCertificate` in ExportOptions; assert TeamIdentifier + Hardened Runtime on the app and every nested Sparkle component after export; thread SPARKLE_ACCOUNT for local keychain runs; require the Sparkle key in the signing gate; fix a stale entitlements comment. Verified: make check, make release-dry, unit tests of the key-encoding and signature-guard logic, and a functional test of the dmg-isolation + guard against the real appcast.sh. Opus adversarial review: no blockers. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016e2Q6fgsBk9ML2jwwxRAbf
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 Developer-ID → notarize → staple → appcast pipeline had never actually run signed (no cert until now). A pre-flight audit of it found 3 defects that would fail a real
v0.1.0release after notarization — the worst place to fail — plus a hazard that could silently ship an unsigned update.Blockers fixed
generate_appcastundiscoverable on a clean CI runner —appcast.shonly searched.build/artifacts, which a fresh checkout lacks. Now it also searches the DerivedData thatxcodebuild archivepopulates, andrelease.ymlwarms.buildviaswift package resolve(continue-on-error, since discovery is already covered).dist/held bothCapsule-<v>.zipand.dmg—generate_appcastrejects same-version duplicates. Now it signs in an isolateddist/appcast-src/holding only the update zip; the dmg is a human download Sparkle never sees.release.ymlnow acceptsSPARKLE_ED_PRIVATE_KEYwhether stored as the raw base64 key string or base64-encoded once more.Guards & hardening
appcast.shfails the build if any<enclosure>lackssparkle:edSignature— a key/SUPublicEDKeymismatch can no longer silently publish an update every client rejects.export.shassertsTeamIdentifier == TEAM_IDand the Hardened Runtime flag on the app and every nested Sparkle component (Autoupdate / Updater.app / XPC services) — catches notarization-fatal mis-signs before submission.signingCertificateso a second/renewed Developer ID cert can't be chosen.SPARKLE_ACCOUNTthreads the keychain account for local runs; the signing gate now also requires the Sparkle key; stale entitlements comment corrected.Verification
make check+make release-drygreen.appcast.sh.mvguard, the gate — are all folded in).After merge: push, then tag
v0.1.0to cut the first signed release.🤖 Generated with Claude Code
https://claude.ai/code/session_016e2Q6fgsBk9ML2jwwxRAbf