Fix Sparkle signing and installer fallback#1962
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (6)
✅ Files skipped from review due to trivial changes (2)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughCentralizes macOS codesigning into a new signing script with CI regression tests for the signing plan, and adds a manual DMG download fallback in the Sparkle update UI and view model for a whitelist of installer error codes. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant UpdateDriver
participant UpdateViewModel
participant NSAlert
participant NSWorkspace
User->>UpdateDriver: Updater reports Sparkle installation error (e.g. SUSparkleErrorDomain 4005)
UpdateDriver->>UpdateViewModel: manualDownloadURL(for: error)
UpdateViewModel-->>UpdateDriver: URL or nil
alt URL available
UpdateDriver->>NSAlert: present manual-download alert (Download, Retry, OK)
NSAlert-->>User: show choices
User->>NSAlert: selects "Download Latest DMG"
NSAlert->>NSWorkspace: open(manualDownloadURL)
NSWorkspace-->>User: browser opens download URL
else no URL
UpdateDriver->>UpdateDriver: fallback to standard error presentation
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 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 |
There was a problem hiding this comment.
2 issues found across 11 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="Sources/Update/UpdateViewModel.swift">
<violation number="1" location="Sources/Update/UpdateViewModel.swift:7">
P2: Manual update fallback is hardcoded to the stable DMG, which sends NIGHTLY users to the wrong installer after Sparkle installation failures.</violation>
</file>
<file name="tests/test_ci_codesign_app_bundle.sh">
<violation number="1" location="tests/test_ci_codesign_app_bundle.sh:43">
P2: Avoid asserting literal workflow file text in this regression test; verify behavior through executable paths/artifacts instead.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3814ad2919
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| import Sparkle | ||
|
|
||
| class UpdateViewModel: ObservableObject { | ||
| static let manualDownloadDMGURLString = "https://github.com/manaflow-ai/cmux/releases/latest/download/cmux-macos.dmg" |
There was a problem hiding this comment.
Choose manual DMG URL by update channel
The new fallback link is hardcoded to the stable asset (releases/latest/download/cmux-macos.dmg), but nightly builds use a different feed and asset (releases/download/nightly/appcast.xml and cmux-nightly-macos.dmg in .github/workflows/nightly.yml). That means NIGHTLY users who hit these Sparkle install errors and click “Download Latest DMG” are sent to the stable installer, which silently switches them off the nightly channel. Please derive the manual-download URL from the resolved update channel/feed instead of using a single stable URL.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@scripts/codesign_app_bundle.sh`:
- Around line 41-45: The sign_without_entitlements helper currently strips
entitlements for every target; detect the special-case XPC service by checking
for "Downloader.xpc" (e.g., basename of the path) and for that path perform
signing that preserves existing entitlements instead of the generic
no-entitlements flow: invoke emit_or_run (the same wrapper used here) with the
signing identity (SIGNING_IDENTITY) but include codesign's
entitlements-preserving option (--preserve-metadata=entitlements) so the XPC's
entitlements metadata is retained; for all other paths keep the existing
sign_without_entitlements behavior.
In `@tests/test_ci_codesign_app_bundle.sh`:
- Around line 88-100: Add a regression check for Downloader.xpc: after the block
that ensures Sparkle components are not signed with the app entitlements
(checking OUTPUT for "--entitlements"), add an assertion that Downloader.xpc
still has its own entitlements metadata preserved by verifying OUTPUT contains a
"--entitlements" entry for "Downloader.xpc" and that the entitlements path for
Downloader.xpc is not the app ENTITLEMENTS variable (OUTPUT and ENTITLEMENTS
should be used to locate the relevant lines). Ensure the check fails the script
if Downloader.xpc lacks any "--entitlements" entry or if its entitlements path
equals $ENTITLEMENTS.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: db2ebb6b-5be2-4eb2-9763-568cc12470ce
📒 Files selected for processing (11)
.github/workflows/ci.yml.github/workflows/nightly.yml.github/workflows/release.ymlResources/Localizable.xcstringsSources/Update/UpdateDriver.swiftSources/Update/UpdatePopoverView.swiftSources/Update/UpdateViewModel.swiftcmuxTests/ShortcutAndCommandPaletteTests.swiftscripts/build-sign-upload.shscripts/codesign_app_bundle.shtests/test_ci_codesign_app_bundle.sh
| sign_without_entitlements() { | ||
| local path="$1" | ||
| [ -e "$path" ] || return 0 | ||
| emit_or_run --force --options runtime --timestamp --sign "$SIGNING_IDENTITY" "$path" | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's verify the file exists and examine the relevant code sections
git ls-files | grep -E "codesign|sign"Repository: manaflow-ai/cmux
Length of output: 4330
🏁 Script executed:
# Check the script at the specified lines
cat -n scripts/codesign_app_bundle.sh | sed -n '1,100p'Repository: manaflow-ai/cmux
Length of output: 3413
🏁 Script executed:
# Search for all uses of the signing helpers to understand context
rg "sign_without_entitlements|sign_preserving_entitlements|Downloader\.xpc" scripts/Repository: manaflow-ai/cmux
Length of output: 677
🌐 Web query:
Sparkle framework Downloader.xpc code signing requirements entitlements
💡 Result:
The Sparkle framework's Downloader.xpc is an optional XPC service used in sandboxed macOS apps without the com.apple.security.network.client entitlement for network downloads during updates. Code signing requirements: - Sign individually (do not use --deep): codesign -f -s "$IDENTITY" -o runtime --preserve-metadata=entitlements [path]/Sparkle.framework/Versions/B/XPCServices/Downloader.xpc (for Sparkle >=2.6; preserves existing entitlements). - For Sparkle <2.6 or manual signing: codesign -f -s "$IDENTITY" -o runtime --entitlements Entitlements/Downloader.entitlements [path]. Downloader.xpc entitlements: - Not sandboxed by default (as of Sparkle 2.6). - Optional sandboxing: requires building Sparkle from source, uncommenting DOWNLOADER_SANDBOXED_ENTITLEMENTS and modifying XPC_SERVICE_BUNDLE_ID_PREFIX in ConfigCommon.xcconfig. - Specific entitlements content (e.g., from Entitlements/Downloader.entitlements) enables network access (likely com.apple.security.network.client) but exact plist not found in searched sources; fetch from Sparkle repo for manual use. - Enable via Info.plist: SUEnableDownloaderService = true (only if app lacks network entitlement). App entitlements for XPC communication (separate from Downloader.xpc itself): com.apple.security.temporary-exception.mach-lookup.global-name #2062, steipete.me post).
Citations:
- 1: https://steipete.me/posts/2025/code-signing-and-notarization-sparkle-and-tears
- 2: https://sparkle-project.org/documentation/sandboxing/
- 3: https://sparkle-project.github.io/documentation/sandboxing
- 4: Preserve the
Entitlementsdirectory in podspec sparkle-project/Sparkle#2062 - 5: https://swiftdevjournal.com/using-the-sparkle-framework-in-a-sandboxed-app/
🌐 Web query:
codesign --preserve-metadata entitlements macOS command syntax
💡 Result:
The syntax for the macOS codesign command option --preserve-metadata related to entitlements is --preserve-metadata=entitlements. This option preserves the existing entitlement data from a prior signature when re-signing already-signed code. It requires --force (-f) to overwrite the existing signature. Full syntax example for re-signing while preserving entitlements: codesign --sign "Your Signing Identity" --force --preserve-metadata=entitlements [--options runtime] [--timestamp] /path/to/binary_or_bundle Other supported values for --preserve-metadata (comma-separated list): - identifier: Preserve signing identifier - requirements: Preserve internal requirements (including Designated Requirement) - flags: Preserve option flags - runtime: Preserve hardened runtime version - launch-constraints: Preserve launch constraints - library-constraints: Preserve library load constraints Without a value (deprecated), it preserves all known metadata. Use explicit lists for precision. To extract existing entitlements for verification: codesign -d --entitlements :- /path/to/binary (writes to stdout). This is used when re-signing to retain entitlements without needing to re-specify an entitlements file via --entitlements path, as seen in .NET apphost signing discussions and Apple Developer examples for DER entitlements.
Citations:
- 1: Codesigning the apphost on macOS should preserve entitlements dotnet/runtime#113707
- 2: https://developer.apple.com/documentation/xcode/using-the-latest-code-signature-format
- 3: https://keith.github.io/xcode-man-pages/codesign.1.html
- 4: https://stackoverflow.com/questions/36888535/how-do-i-resign-app-with-entitlements
Special-case Downloader.xpc to preserve its existing entitlements during code signing.
Sparkle's official signing guide requires that Downloader.xpc preserve its entitlements metadata when re-signed for Sparkle 2.6+. Using the generic sign_without_entitlements path here strips that metadata, breaking the XPC service's functionality. (sparkle-project.org/documentation/sandboxing)
🛠️ Suggested change
sign_without_entitlements() {
local path="$1"
[ -e "$path" ] || return 0
emit_or_run --force --options runtime --timestamp --sign "$SIGNING_IDENTITY" "$path"
}
+sign_preserving_entitlements() {
+ local path="$1"
+ [ -e "$path" ] || return 0
+ emit_or_run --force --options runtime --timestamp --sign "$SIGNING_IDENTITY" \
+ --preserve-metadata=entitlements "$path"
+}
+
sign_with_entitlements() {
local path="$1"
[ -e "$path" ] || return 0
emit_or_run --force --options runtime --timestamp --sign "$SIGNING_IDENTITY" --entitlements "$ENTITLEMENTS_PATH" "$path"
}
@@
if [ -n "$SPARKLE_VERSION_DIR" ]; then
sign_without_entitlements "$SPARKLE_VERSION_DIR/XPCServices/Installer.xpc"
- sign_without_entitlements "$SPARKLE_VERSION_DIR/XPCServices/Downloader.xpc"
+ sign_preserving_entitlements "$SPARKLE_VERSION_DIR/XPCServices/Downloader.xpc"
sign_without_entitlements "$SPARKLE_VERSION_DIR/Autoupdate"
sign_without_entitlements "$SPARKLE_VERSION_DIR/Updater.app"
fi🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@scripts/codesign_app_bundle.sh` around lines 41 - 45, The
sign_without_entitlements helper currently strips entitlements for every target;
detect the special-case XPC service by checking for "Downloader.xpc" (e.g.,
basename of the path) and for that path perform signing that preserves existing
entitlements instead of the generic no-entitlements flow: invoke emit_or_run
(the same wrapper used here) with the signing identity (SIGNING_IDENTITY) but
include codesign's entitlements-preserving option
(--preserve-metadata=entitlements) so the XPC's entitlements metadata is
retained; for all other paths keep the existing sign_without_entitlements
behavior.
| for sparkle_component in Installer.xpc Downloader.xpc /Autoupdate Updater.app Sparkle.framework Sentry.framework; do | ||
| if printf '%s\n' "$OUTPUT" | grep -F "$sparkle_component" | grep -Fq -- '--entitlements'; then | ||
| echo "FAIL: $sparkle_component must not be signed with app entitlements" | ||
| exit 1 | ||
| fi | ||
| done | ||
|
|
||
| for entitled_component in /Resources/bin/cmux /Resources/bin/ghostty /cmux.app; do | ||
| if ! printf '%s\n' "$OUTPUT" | grep -F "$entitled_component" | grep -Fv -- '--verify' | grep -Fq -- "--entitlements $ENTITLEMENTS"; then | ||
| echo "FAIL: $entitled_component must be signed with app entitlements" | ||
| exit 1 | ||
| fi | ||
| done |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's examine the test file to understand the full context
find . -name "*test_ci_codesign_app_bundle*" -type fRepository: manaflow-ai/cmux
Length of output: 98
🏁 Script executed:
# Also find the codesign_app_bundle.sh script
find . -name "codesign_app_bundle.sh" -type fRepository: manaflow-ai/cmux
Length of output: 92
🏁 Script executed:
# Get the repository structure to understand the layout
git ls-files | head -20Repository: manaflow-ai/cmux
Length of output: 675
🏁 Script executed:
# Read the test file to see full context around lines 88-100
wc -l ./tests/test_ci_codesign_app_bundle.shRepository: manaflow-ai/cmux
Length of output: 102
🏁 Script executed:
# Read the codesign script
wc -l ./scripts/codesign_app_bundle.shRepository: manaflow-ai/cmux
Length of output: 95
🏁 Script executed:
# Read the full test file
cat -n ./tests/test_ci_codesign_app_bundle.shRepository: manaflow-ai/cmux
Length of output: 4807
🏁 Script executed:
# Read the full codesign script
cat -n ./scripts/codesign_app_bundle.shRepository: manaflow-ai/cmux
Length of output: 3413
🌐 Web query:
Sparkle framework Downloader.xpc entitlements sandboxing --preserve-metadata
💡 Result:
In the Sparkle framework for macOS app updates, Downloader.xpc is an optional XPC service bundled in Sparkle.framework/Versions/B/XPCServices/ for sandboxed apps lacking the com.apple.security.network.client entitlement. It enables downloads without granting network access to the main app. By default (Sparkle 2.6+), Downloader.xpc is not sandboxed. To sandbox it (optional), build Sparkle from source by uncommenting DOWNLOADER_SANDBOXED_ENTITLEMENTS and setting XPC_SERVICE_BUNDLE_ID_PREFIX in ConfigCommon.xcconfig. For code signing in manual/custom workflows (e.g., non-Xcode), use --preserve-metadata=entitlements to retain any existing entitlements on Downloader.xpc: codesign -f -s "$CODE_SIGN_IDENTITY" -o runtime --preserve-metadata=entitlements "$APP_BUNDLE/Contents/Frameworks/Sparkle.framework/Versions/B/XPCServices/Downloader.xpc" This preserves sandbox entitlements if present (e.g., from source build). For Sparkle <2.6 or explicit application, use --entitlements Entitlements/Downloader.entitlements (Entitlements/ dir in Sparkle source for sandboxed Downloader). Xcode archive/export preserves entitlements automatically. Avoid --deep signing; sign components individually as entitlements differ (Downloader may be sandboxed, Installer.xpc is not). Enable via SUEnableDownloaderService=YES in app Info.plist if needed. Main app needs com.apple.security.temporary-exception.mach-lookup.global-name for XPC communication.
Citations:
- 1: https://sparkle-project.github.io/documentation/sandboxing
- 2: https://adamwulf.me/2023/06/integrating-sparkle-framework-in-sandboxed-mac-catalyst-app/
- 3: https://sparkle-project.org/documentation/sandboxing/
- 4: Move XPC Services inside Sparkle framework sparkle-project/Sparkle#1965
- 5: https://steipete.me/posts/2025/code-signing-and-notarization-sparkle-and-tears
Add a regression check for Downloader.xpc's preserved entitlements.
This test only proves that Downloader.xpc does not inherit the app entitlements. It does not lock in Sparkle's documented special case for preserving Downloader.xpc's own entitlements metadata, so CI would still stay green if the helper regressed there. (sparkle-project.org)
🧪 Suggested assertion
-for sparkle_component in Installer.xpc Downloader.xpc /Autoupdate Updater.app Sparkle.framework Sentry.framework; do
+for sparkle_component in Installer.xpc /Autoupdate Updater.app Sparkle.framework Sentry.framework; do
if printf '%s\n' "$OUTPUT" | grep -F "$sparkle_component" | grep -Fq -- '--entitlements'; then
echo "FAIL: $sparkle_component must not be signed with app entitlements"
exit 1
fi
done
+
+if ! printf '%s\n' "$OUTPUT" | grep -F 'Downloader.xpc' | grep -Fq -- '--preserve-metadata=entitlements'; then
+ echo "FAIL: Downloader.xpc must preserve its own entitlements when re-signed"
+ exit 1
+fi
+
+if printf '%s\n' "$OUTPUT" | grep -F 'Downloader.xpc' | grep -Fq -- "--entitlements $ENTITLEMENTS"; then
+ echo "FAIL: Downloader.xpc must not inherit app entitlements"
+ exit 1
+fi🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@tests/test_ci_codesign_app_bundle.sh` around lines 88 - 100, Add a regression
check for Downloader.xpc: after the block that ensures Sparkle components are
not signed with the app entitlements (checking OUTPUT for "--entitlements"), add
an assertion that Downloader.xpc still has its own entitlements metadata
preserved by verifying OUTPUT contains a "--entitlements" entry for
"Downloader.xpc" and that the entitlements path for Downloader.xpc is not the
app ENTITLEMENTS variable (OUTPUT and ENTITLEMENTS should be used to locate the
relevant lines). Ensure the check fails the script if Downloader.xpc lacks any
"--entitlements" entry or if its entitlements path equals $ENTITLEMENTS.
There was a problem hiding this comment.
🧹 Nitpick comments (1)
Sources/Update/UpdateViewModel.swift (1)
348-354: The edge case is handled gracefully but relies entirely on CI injection of SUFeedURL for nightly builds.When
SUFeedURLis missing,UpdateFeedResolver.resolvedFeedURLStringintentionally falls back to the stable appcast feed and setsisNightly = false. This is tested behavior. However, nightly builds depend entirely on the build script (scripts/build-sign-upload.sh:90) injecting the correct nightly appcast URL with/nightly/in the path for theisNightlydetection to work. If that injection fails, a nightly build would request the stable DMG. While unlikely in CI-controlled production builds, it might be worth adding a secondary heuristic (e.g., checkingCFBundleVersionfor nightly markers) as a belt-and-suspenders approach.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@Sources/Update/UpdateViewModel.swift` around lines 348 - 354, manualDownloadURL(for:) currently relies solely on SUFeedURL + UpdateFeedResolver.resolvedFeedURLString to decide isNightly; add a fallback heuristic that checks Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") (or another infoplist key) for a nightly marker (e.g., contains "nightly" or a known nightly suffix/prefix) when infoFeedURL is nil or UpdateFeedResolver returns .isNightly == false; update the logic in UpdateViewModel.manualDownloadURL(for:) to treat the build as nightly if either UpdateFeedResolver.resolvedFeedURLString(infoFeedURL: infoFeedURL).isNightly OR the CFBundleVersion heuristic matches, so nightly DMG URL is returned even if SUFeedURL injection failed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@Sources/Update/UpdateViewModel.swift`:
- Around line 348-354: manualDownloadURL(for:) currently relies solely on
SUFeedURL + UpdateFeedResolver.resolvedFeedURLString to decide isNightly; add a
fallback heuristic that checks Bundle.main.object(forInfoDictionaryKey:
"CFBundleVersion") (or another infoplist key) for a nightly marker (e.g.,
contains "nightly" or a known nightly suffix/prefix) when infoFeedURL is nil or
UpdateFeedResolver returns .isNightly == false; update the logic in
UpdateViewModel.manualDownloadURL(for:) to treat the build as nightly if either
UpdateFeedResolver.resolvedFeedURLString(infoFeedURL: infoFeedURL).isNightly OR
the CFBundleVersion heuristic matches, so nightly DMG URL is returned even if
SUFeedURL injection failed.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: a51a680f-3795-40f8-be12-5b097afca89f
📒 Files selected for processing (3)
Sources/Update/UpdateViewModel.swiftcmuxTests/ShortcutAndCommandPaletteTests.swifttests/test_ci_codesign_app_bundle.sh
🚧 Files skipped from review as they are similar to previous changes (1)
- tests/test_ci_codesign_app_bundle.sh
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 64474cc51e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| sign_without_entitlements() { | ||
| local path="$1" | ||
| [ -e "$path" ] || return 0 | ||
| emit_or_run --force --options runtime --timestamp --sign "$SIGNING_IDENTITY" "$path" |
There was a problem hiding this comment.
Preserve Sparkle entitlements when re-signing helpers
This helper re-signs Sparkle components via --force --sign without --entitlements or --preserve-metadata=entitlements, which means the existing signature metadata is not retained during release/nightly signing. As a result, Sparkle’s nested helpers (Installer/Downloader/Updater) can lose their embedded entitlements, contradicting the script’s stated goal to keep Sparkle helper entitlements and potentially reintroducing update-install failures in signed production builds.
Useful? React with 👍 / 👎.
Greptile SummaryThis PR addresses two related issues with Sparkle auto-updates: it consolidates all codesigning into a shared helper script that signs the bundle inside-out (Sparkle XPC helpers first, then the framework, then other dependencies, then the app) without Key changes:
Confidence Score: 5/5
Important Files Changed
Sequence DiagramsequenceDiagram
participant Sparkle
participant UpdateDriver
participant UpdateViewModel
participant NSAlert
participant UpdateErrorView
Sparkle->>UpdateDriver: showUpdaterError(error)
UpdateDriver->>UpdateViewModel: manualDownloadURL(for: error)
alt Standard presentation & installation error (4000-4012)
UpdateViewModel-->>UpdateDriver: URL("…/cmux-macos.dmg")
UpdateDriver->>NSAlert: presentManualDownloadAlert(error, url)
NSAlert-->>UpdateDriver: .alertFirstButtonReturn (Download DMG)
UpdateDriver->>NSWorkspace: open(manualDownloadURL)
UpdateDriver->>Sparkle: acknowledgement()
else Standard presentation, other error
UpdateViewModel-->>UpdateDriver: nil
UpdateDriver->>Sparkle: standard.showUpdaterError(error)
else Custom presentation
UpdateViewModel-->>UpdateDriver: (not called)
UpdateDriver->>UpdateViewModel: setState(.error(...))
UpdateViewModel-->>UpdateErrorView: render error state
UpdateErrorView->>UpdateViewModel: manualDownloadURL(for: error)
alt Installation error
UpdateViewModel-->>UpdateErrorView: URL("…/cmux-macos.dmg")
UpdateErrorView->>NSWorkspace: open(manualDownloadURL)
end
end
|
| if ! printf '%s\n' "$OUTPUT" | grep -F "$entitled_component" | grep -Fv -- '--verify' | grep -Fq -- "--entitlements $ENTITLEMENTS"; then | ||
| echo "FAIL: $entitled_component must be signed with app entitlements" | ||
| exit 1 | ||
| fi |
There was a problem hiding this comment.
Ordering assertion missing
cli_line < helper_line check
The compound ordering guard checks sentry_line < cli_line and helper_line < app_line, but skips checking that cli_line < helper_line. If the script were ever reordered so that bin/ghostty was signed before bin/cmux, this test would not catch the regression.
| if ! printf '%s\n' "$OUTPUT" | grep -F "$entitled_component" | grep -Fv -- '--verify' | grep -Fq -- "--entitlements $ENTITLEMENTS"; then | |
| echo "FAIL: $entitled_component must be signed with app entitlements" | |
| exit 1 | |
| fi | |
| if [ "$sparkle_line" -ge "$sentry_line" ] || [ "$sentry_line" -ge "$cli_line" ] || [ "$cli_line" -ge "$helper_line" ] || [ "$helper_line" -ge "$app_line" ] || [ "$app_line" -ge "$verify_line" ]; then |
| resolve_sparkle_version_dir() { | ||
| local sparkle_framework="$1" | ||
| local current_dir="$sparkle_framework/Versions/Current" | ||
|
|
||
| if [ -d "$current_dir" ]; then | ||
| printf '%s\n' "$current_dir" | ||
| return 0 | ||
| fi | ||
|
|
||
| find "$sparkle_framework/Versions" -mindepth 1 -maxdepth 1 -type d ! -name Current -print | LC_ALL=C sort | head -n 1 | ||
| } |
There was a problem hiding this comment.
resolve_sparkle_version_dir returns the Current symlink path, not the canonical path
When Versions/Current exists as a symlink (the normal case), the function returns $sparkle_framework/Versions/Current — the symlink itself — rather than resolving it to the concrete directory (e.g. Versions/B). While codesign follows symlinks on macOS and this won't cause signing failures in practice, it means the paths recorded in any log or dry-run output contain …/Versions/Current/… rather than …/Versions/B/…, which can be confusing when debugging. Consider resolving the symlink explicitly:
if [ -L "$current_dir" ]; then
printf '%s\n' "$(readlink -f "$current_dir")"
return 0
elif [ -d "$current_dir" ]; then
printf '%s\n' "$current_dir"
return 0
fi
Summary
--deep, using a shared codesign helper for nightly and release buildsTesting
Closes #1960
Closes #1961
Summary by cubic
Hardens macOS update flow by signing the app bundle inside‑out (no --deep) and adding a manual “Download Latest DMG” path when Sparkle install fails.
Bug Fixes
scripts/codesign_app_bundle.sh: resolvesSparkle.frameworkVersions/Current, signs XPCs/Autoupdate/Updater.appfirst, then other frameworks, then the app and bundled CLIs; avoids--deepand keeps app entitlements off Sparkle components.release.yml,nightly.yml, andbuild-sign-upload.sh; CI runstests/test_ci_codesign_app_bundle.sh(dry‑run) to verify signing order and flags.New Features
Written for commit f77e7d8. Summary will update on new commits.
Summary by CodeRabbit
New Features
Localization
CI
Tests