Repository navigation
Fix nightly codesign: sign Sparkle nested executables and dock tile plugin - #2677
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
This review could not be run because your cubic account has exceeded the monthly review limit. If you need help restoring access, please contact contact@cubic.dev. |
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughThe pull request restructures the macOS codesigning process in two GitHub Actions workflows by replacing a single nested bundle signing pass with three explicit deepest-first passes: one for nested Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Possibly related PRs
Poem
✨ Finishing Touches🧪 Generate unit tests (beta)
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 |
The single-pass approach only signed bundle directories (.framework, .plugin) but not the standalone executables and nested apps inside them. Sparkle.framework contains Updater.app and Autoupdate which need to be individually signed before the framework itself. Three-pass signing order: 1) Nested .app/.xpc bundles inside frameworks (deepest-first) 2) Standalone Mach-O executables & dylibs (e.g. Sparkle's Autoupdate) 3) .framework/.plugin/.appex bundles (deepest-first) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
c440b0f to
18d9fca
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is kicking off a free cloud agent to fix this issue. This run is complimentary, but you can enable autofix for all future PRs in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 18d9fca. Configure here.
| if file "$f" | grep -qE 'Mach-O'; then | ||
| /usr/bin/codesign --force --options runtime --timestamp --sign "$APPLE_SIGNING_IDENTITY" --entitlements "$EMBEDDED_ENTITLEMENTS" "$f" | ||
| fi | ||
| done < <(find "$DIR" -type f \( -perm +111 -o -name '*.dylib' \) -not -path '*.app/*' -print0) |
There was a problem hiding this comment.
Pass 2 invalidates .xpc bundle signatures from Pass 1
High Severity
Pass 2's find excludes only *.app/* paths but not *.xpc/*. Sparkle 2.x bundles XPC services (e.g., Installer.xpc, Downloader.xpc) inside the framework. Pass 1 signs these .xpc bundles, then Pass 2 discovers and re-signs their internal main executables (which are Mach-O files with +x), invalidating the .xpc bundle signatures. Since Pass 3 only re-signs .framework/.plugin/.appex — not .xpc — the corrupted .xpc signatures persist, causing codesign --verify --deep --strict to fail.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 18d9fca. Configure here.
Greptile SummaryThis PR introduces a three-pass deepest-first signing loop to fix the unsigned nested bundles (
Confidence Score: 2/5Not safe to merge — two independent defects in the new signing logic will cause the CI build to fail at either deep verification or Apple notarization. Score of 2 reflects two P1 issues: the missing -not -path '.xpc/' guard invalidates XPC service bundle signatures and the deep-verify step will surface this immediately; separately, applying camera/microphone entitlements to Sparkle's helper components will cause Apple notarization to reject those binaries. Either issue alone breaks the build. Both .github/workflows/nightly.yml (lines 421-426 and 416-431) and .github/workflows/release.yml (lines 282-287 and 278-292) require the same two fixes before this can land. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Build .app bundle] --> B[Sign CLI & Ghostty helpers\nwith EMBEDDED_ENTITLEMENTS]
B --> C[For each: PlugIns + Frameworks dirs]
C --> P1[Pass 1: sign .app and .xpc bundles\ndepth-first — seals XPC CodeResources]
P1 --> P2[Pass 2: sign Mach-O executables + dylibs\n-not -path '.app/*' only]
P2 --> BUG1{Executable inside .xpc bundle?}
BUG1 -- Yes, not excluded --> BREAK[Re-signs inner binary\nbreaks outer .xpc seal]
BUG1 -- No --> P3
BREAK --> P3[Pass 3: sign .framework/.plugin/.appex]
P3 --> OUTER[Sign outer .app]
OUTER --> VERIFY{codesign --verify --deep --strict}
VERIFY -- broken .xpc seal --> FAIL1[CI FAILS]
VERIFY -- OK --> NOTARIZE[Submit to Apple notarytool]
NOTARIZE --> BUG2{Camera/mic entitlements\non Sparkle helpers?}
BUG2 -- Yes via EMBEDDED_ENTITLEMENTS --> FAIL2[Notarization REJECTED\nno usage descriptions]
BUG2 -- No --> SUCCESS[Signed + notarized app]
Reviews (1): Last reviewed commit: "Fix codesign: three-pass signing for Spa..." | Re-trigger Greptile |
| # Pass 2: standalone Mach-O executables & dylibs (skip those inside .app bundles) | ||
| while IFS= read -r -d '' f; do | ||
| if file "$f" | grep -qE 'Mach-O'; then | ||
| /usr/bin/codesign --force --options runtime --timestamp --sign "$APPLE_SIGNING_IDENTITY" --entitlements "$EMBEDDED_ENTITLEMENTS" "$f" | ||
| fi | ||
| done < <(find "$DIR" -type f \( -perm +111 -o -name '*.dylib' \) -not -path '*.app/*' -print0) |
There was a problem hiding this comment.
Pass 2 breaks XPC bundle signatures created in Pass 1
Pass 1 (line 419) signs .xpc bundles deepest-first — including Sparkle 2.x's InstallerLauncher.xpc and InstallerStatus.xpc. Pass 2 then re-signs individual executables inside those already-sealed bundles because the filter excludes only *.app/*, not *.xpc/*. Re-signing a file inside a signed bundle changes its content, so the outer bundle's _CodeSignature/CodeResources hash no longer matches; the XPC bundle is left with a broken signature. Pass 3 never re-signs .xpc bundles, so codesign --verify --deep --strict (line 435) will fail. The identical gap exists in release.yml line 287.
| # Pass 2: standalone Mach-O executables & dylibs (skip those inside .app bundles) | |
| while IFS= read -r -d '' f; do | |
| if file "$f" | grep -qE 'Mach-O'; then | |
| /usr/bin/codesign --force --options runtime --timestamp --sign "$APPLE_SIGNING_IDENTITY" --entitlements "$EMBEDDED_ENTITLEMENTS" "$f" | |
| fi | |
| done < <(find "$DIR" -type f \( -perm +111 -o -name '*.dylib' \) -not -path '*.app/*' -print0) | |
| done < <(find "$DIR" -type f \( -perm +111 -o -name '*.dylib' \) -not -path '*.app/*' -not -path '*.xpc/*' -print0) |
| # Pass 1: nested .app and .xpc bundles (deepest-first) | ||
| while IFS= read -r -d '' bundle; do | ||
| /usr/bin/codesign --force --options runtime --timestamp --sign "$APPLE_SIGNING_IDENTITY" --entitlements "$EMBEDDED_ENTITLEMENTS" "$bundle" | ||
| done < <(find "$DIR" -depth -type d \( -name '*.plugin' -o -name '*.appex' -o -name '*.framework' -o -name '*.xpc' \) -print0) | ||
| done < <(find "$DIR" -depth -type d \( -name '*.app' -o -name '*.xpc' \) -print0) | ||
|
|
||
| # Pass 2: standalone Mach-O executables & dylibs (skip those inside .app bundles) | ||
| while IFS= read -r -d '' f; do | ||
| if file "$f" | grep -qE 'Mach-O'; then | ||
| /usr/bin/codesign --force --options runtime --timestamp --sign "$APPLE_SIGNING_IDENTITY" --entitlements "$EMBEDDED_ENTITLEMENTS" "$f" | ||
| fi | ||
| done < <(find "$DIR" -type f \( -perm +111 -o -name '*.dylib' \) -not -path '*.app/*' -print0) | ||
|
|
||
| # Pass 3: .framework, .plugin, .appex bundles (deepest-first) | ||
| while IFS= read -r -d '' bundle; do | ||
| /usr/bin/codesign --force --options runtime --timestamp --sign "$APPLE_SIGNING_IDENTITY" --entitlements "$EMBEDDED_ENTITLEMENTS" "$bundle" | ||
| done < <(find "$DIR" -depth -type d \( -name '*.framework' -o -name '*.plugin' -o -name '*.appex' \) -print0) |
There was a problem hiding this comment.
App-level entitlements with camera/microphone applied to Sparkle's helper components
cmux.embedded.entitlements includes com.apple.security.device.camera and com.apple.security.device.audio-input. All three passes stamp these entitlements onto every nested bundle and executable — including Sparkle's Autoupdate, Updater.app, and its XPC services. Apple's notarization service rejects any hardened-runtime binary that claims camera or microphone access without a corresponding NSCameraUsageDescription / NSMicrophoneUsageDescription in that binary's own Info.plist. Sparkle's helpers carry neither, so notarization will fail on those binaries. The same issue exists in release.yml lines 278–292.
Sparkle's internals should be signed with no custom entitlements file (or a minimal one containing only the cs.* exceptions actually needed by Ghostty). Reserve --entitlements "$EMBEDDED_ENTITLEMENTS" for the app's own CLI and helper binaries, not for third-party framework components.


Summary
.app/.xpcbundles, standalone Mach-O executables (Sparkle'sAutoupdate), then.framework/.plugin/.appexbundlesnightly.ymlandrelease.ymlRoot cause
The failing nightly run failed because:
CmuxDockTilePlugin.pluginwas not in the manual sign loopUpdater.appandAutoupdateexecutable were not individually signed with Developer ID + timestampSigning a
.frameworkbundle only signs its main binary — nested executables and app bundles inside it must be signed individually.Fix
Before signing the outer
.app, the workflow now runs three passes:.app/.xpcbundles inside frameworks (e.g. Sparkle'sUpdater.app).appbundles (e.g. Sparkle'sAutoupdate).framework/.plugin/.appexbundles themselvesFiles changed
.github/workflows/nightly.yml.github/workflows/release.yml🤖 Generated with Claude Code
Note
Medium Risk
Changes release/nightly CI signing order and file-selection logic, which can break notarization if the find/sign patterns miss or incorrectly sign embedded binaries.
Overview
Updates the macOS
nightly.ymlandrelease.ymlcodesigning steps to sign nested components explicitly before signing the outer.app.Replaces the single “sign nested bundles” sweep with a three-pass deepest-first process: (1) nested
.app/.xpcbundles, (2) standalone embedded Mach-O executables and.dylibs (excluding those inside.appbundles), then (3).framework/.plugin/.appexbundles, improvingcodesign --verifyand notarization reliability (e.g., Sparkle nested helpers / dock tile plugin).Reviewed by Cursor Bugbot for commit 18d9fca. Bugbot is set up for automated code reviews on this repo. Configure here.
Summary by cubic
Fixes codesign in nightly and release by deep-signing nested apps and executables before the outer app. Adds a deepest-first three-pass sweep to stop verify failures and allow notarized builds.
CmuxDockTilePlugin.pluginin Contents/PlugIns.Updater.appandAutoupdatebeforeSparkle.framework.Contents/FrameworksandContents/PlugIns: 1).app/.xpc, 2) standalone Mach‑O execs and.dylib(skip inside.app), 3).framework/.plugin/.appex..github/workflows/nightly.ymland.github/workflows/release.yml.Written for commit 18d9fca. Summary will update on new commits.
Summary by CodeRabbit