ci: let the nightly channel sign with its own Sparkle key - #14215
Merged
teamleaderleo merged 2 commits intoSep 24, 2026
Merged
Conversation
nightly.yml derived SUPublicEDKey from, and signed the appcast with, the same SPARKLE_PRIVATE_KEY stable releases use. A machine that signs nightlies could therefore sign a stable update. NIGHTLY_SPARKLE_KEY=nightly now moves the nightly channel onto the NIGHTLY_SPARKLE_PRIVATE_KEY secret. Both the embedded public key and the appcast signature come from scripts/ci/nightly-sparkle-key.sh, so they cannot disagree, and a missing secret fails instead of falling back. Unset keeps today's key; the rc channel never moves. Installed nightlies accept the change without a transition build because Sparkle allows an EdDSA key rotation while the Developer ID stays the same. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Warning Review limit reachedNext included review available in 4 minutes. View limit detailsLimit details: You’ve used all 10 included reviews currently available. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Review configuration: ⚙️ Run configurationConfiguration used: Repository: manaflow-ai/cmux/.coderabbit.yaml Review profile: ASSERTIVE Plan: Advanced Run ID: 📒 Files selected for processing (7)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Contributor
|
All contributors have signed the CLA ✍️ ✅ |
Sparkle validates deltas against the installed app's key only, so after NIGHTLY_SPARKLE_KEY changes the key, deltas from builds embedding the other key are always rejected. Drop those previous DMGs before generate_appcast instead of building deltas clients refuse. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
rustybret
pushed a commit
to rustybret/bmux
that referenced
this pull request
Sep 24, 2026
dd7ea7c ci: let the nightly channel sign with its own Sparkle key (manaflow-ai#14215) b9d4df0 ci: reject errno read inside a Swift test assertion (manaflow-ai#14054) 4e35c3e ci: pin the Glaeda candidate that accepts cmux's current Xcode pins (manaflow-ai#14213) 1c1d27b ci: try the nightly app compile on an owned Mac mini first, Blacksmith fallback (manaflow-ai#14208) 1fc4b83 refactor: move the surface catalog's value types into a package (manaflow-ai#13135) 2ee69dd refactor: move 38 leaf mobile-host files into a CmuxMobileHost package (manaflow-ai#14093) ad5ea20 test(ssh): assert the cmux-tui open flow for TTY cmux ssh (manaflow-ai#14204) 03d3759 test: repair the app-host suites that fail only on macOS 26 (manaflow-ai#13988)
teamleaderleo
added a commit
that referenced
this pull request
Sep 24, 2026
…14243) Reverts the owned-Mac nightly route (#14208, #14223, #14233). There is no separate nightly lane or fallback: nightlies build on Blacksmith until Glaeda routing (glaeda#1174) sends every job std > light > Blacksmith > GitHub-hosted. nightly.yml is back to its pre-lane Blacksmith path, keeping the later nightly Sparkle key change (#14215). Signing, notarization and publication are unchanged. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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.
Nightly and stable releases sign updates with the same Sparkle key (
SPARKLE_PRIVATE_KEY), so anything that can sign a nightly can also sign a stable update. That blocks moving nightly signing onto a Mac mini (#14207, cmuxterm-hq#571). This PR gives the nightly channel its own key, behind a switch that is off by default.What changes.
NIGHTLY_SPARKLE_KEY=nightly(repository variable) makes nightly builds embed and sign with theNIGHTLY_SPARKLE_PRIVATE_KEYsecret. The embeddedSUPublicEDKeyand the appcast signature both come fromscripts/ci/nightly-sparkle-key.sh, so they can't drift apart. If the switch is on and the secret is missing, the build fails rather than quietly using the shared key. Unset, nothing changes. Thercchannel always keeps the shared key.Why installed nightlies keep updating. Sparkle accepts an update that changes the EdDSA key when the app stays code signed with the same Apple Developer ID (docs, "Rotating signing keys"). cmux doesn't set
SUVerifyUpdateBeforeExtraction, so the DMG condition in that section doesn't apply. One rule follows: never change the Developer ID certificate in the same build as this switch.That rule covers full updates only. Sparkle checks a delta against the installed app's key alone, so deltas from builds that embed the other key could never install. The appcast step now drops those previous DMGs before building deltas (
scripts/ci/drop-previous-nightlies-with-other-sparkle-key.sh). The first build after a switch ships without deltas, and clients take the full DMG. For about two builds,generate_appcastalso leaves the older feed items unsigned with a warning; clients take the newest item, which is signed.Limit, stated once. This protects stable users from a leaked nightly Sparkle key. It doesn't protect them from a leaked Developer ID certificate, because Sparkle's rotation rule would accept a Developer ID signed stable update with an attacker's EdDSA key. What still stops that is that the stable appcast is only written by the hosted publish job, whose R2 credentials never go to a mini.
To turn it on (maintainer)
generate_keysfrom Sparkle, with a new keychain account name so it doesn't overwrite the shared key. Export the private key.NIGHTLY_SPARKLE_PRIVATE_KEY.gh variable set NIGHTLY_SPARKLE_KEY --repo manaflow-ai/cmux -b nightlyRollback is deleting the variable. The build after that goes back to the shared key under the same rotation rule.
Validation
tests/test_nightly_sparkle_key.sh(new, runs in therelease-notaryguard group): the variable off, on, and misspelled; the rc channel; a missing secret; and thatnightly.ymlnever passesSPARKLE_PRIVATE_KEYstraight fromsecrets.test_nightly_universal_build.sh,test_sparkle_generate_appcast_no_deltas.sh,test_ci_self_hosted_guard.sh,test_ci_change_areas.py,test_ci_workflow_guards_are_wired.py,actionlintandscripts/verify-local.pypass.drop-previous-nightlies-with-other-sparkle-key.shchecked locally against two throwaway DMGs: it keeps the matching one and drops the other.SUUpdateValidatorandAppInstaller. It confirmed the full-update rotation and found the delta problem fixed above.🤖 Generated with Claude Code
Summary by cubic
Stops nightly builds from signing with the shared Sparkle key stable releases use, so a machine that signs nightlies can no longer sign a stable update. The nightly channel gets its own key behind a switch that is off by default; this protects stable users from a leaked nightly key, though not from a leaked Developer ID certificate, which Sparkle's rotation rule would still accept. Because Sparkle validates deltas against the installed app's key only, a key change makes deltas from older builds uninstallable, so those previous DMGs are dropped before appcast generation.
Migration
NIGHTLY_SPARKLE_KEY=nightlyand save a new key as theNIGHTLY_SPARKLE_PRIVATE_KEYsecret to enable it.rcchannel always keeps the shared key.Written for commit 4de5394. Summary will update on new commits.