From b25c2def92ad0777c319001d0956572bb2bf5ecd Mon Sep 17 00:00:00 2001 From: Bob Date: Thu, 9 Apr 2026 20:43:43 +0000 Subject: [PATCH 1/2] fix(build): sign canonical framework binary once and sync to duplicates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PyInstaller copies Python.framework contents as separate regular files — Python, Versions/Current/Python, and Versions/3.9/Python have identical content but different inodes. The previous approach signed each independently via temp copy, producing 3 different signature blocks (different timestamps, random nonces). Apple's notarization service detects these as inconsistently signed and reports: 'The signature of the binary is invalid.' for all three paths, even though each individual signature is technically valid. Fix: sign only the first (canonical) binary via temp copy, then copy the signed result to all duplicate paths. All three end up with byte-identical content including the embedded signature, so Apple's hash check passes consistently across every path. --- scripts/package/build_app_tauri.sh | 75 ++++++++++++++++-------------- 1 file changed, 41 insertions(+), 34 deletions(-) diff --git a/scripts/package/build_app_tauri.sh b/scripts/package/build_app_tauri.sh index 87879f7f0..9cbe2d70e 100755 --- a/scripts/package/build_app_tauri.sh +++ b/scripts/package/build_app_tauri.sh @@ -153,46 +153,53 @@ if [ -n "$APPLE_PERSONALID" ]; then --sign "$APPLE_PERSONALID" \ "$fw" 2>&1) && echo " Signed bundle: $fw" || { if echo "$sign_output" | grep -q "bundle format is ambiguous"; then - echo " Note: $fw lacks standard bundle structure; signing all Mach-O binaries inside via temp copy" + echo " Note: $fw lacks standard bundle structure; signing canonical Mach-O binary and syncing to duplicates" # PyInstaller copies Python.framework contents as separate files rather # than symlinks — Python, Versions/Current/Python, and Versions/3.9/Python - # are distinct inodes. Signing only $fw_name leaves the Versions/ copies - # unsigned, causing Apple notarization to reject every affected watcher. - # Sign every Mach-O file inside the framework via a temp-path copy to - # avoid the in-place "bundle format is ambiguous" error from codesign. - signed_count=0 + # are distinct inodes with identical content. + # + # Signing each independently produces three different signatures (different + # timestamps, different random nonces in the signature block). Apple's + # notarization service detects these as inconsistently signed and reports + # "The signature of the binary is invalid" for all three paths. + # + # Fix: sign only the FIRST (canonical) binary via temp copy, then copy + # the signed result to all duplicate paths. All three end up with byte- + # identical content including the embedded signature, so Apple's hash + # check passes for every path. + fw_bins=() while IFS= read -r fw_bin; do - echo " Signing framework binary via temp copy: $fw_bin" - # Preserve the binary's existing code-signing identifier. - # Without --identifier, codesign uses the random temp filename - # (e.g. "tmp.XXXXXX") as the identifier, which makes Apple's - # notarization service report "The signature of the binary is - # invalid" — even though the certificate chain and code hashes - # are valid. Using the original identifier (e.g. "org.python.python" - # from PyInstaller's codesign_identity step) or falling back to the - # binary's filename avoids this rejection. - existing_id=$(codesign -d "$fw_bin" 2>&1 \ - | sed -n 's/^Identifier=//p' || true) - if [ -z "$existing_id" ]; then - existing_id=$(basename "$fw_bin") - fi - echo " Using identifier: $existing_id" - tmp_binary=$(mktemp) - cp "$fw_bin" "$tmp_binary" - codesign --force --options runtime --timestamp \ - --entitlements "$ENTITLEMENTS" \ - --identifier "$existing_id" \ - --sign "$APPLE_PERSONALID" \ - "$tmp_binary" || { rm -f "$tmp_binary"; exit 1; } - cp "$tmp_binary" "$fw_bin" || { rm -f "$tmp_binary"; exit 1; } - rm -f "$tmp_binary" - signed_count=$((signed_count + 1)) - done < <(find "$fw" -type f | xargs file | grep "Mach-O" | cut -d: -f1) - if [ "$signed_count" -eq 0 ]; then + fw_bins+=("$fw_bin") + done < <(find "$fw" -type f | xargs file | grep "Mach-O" | cut -d: -f1 | sort) + if [ "${#fw_bins[@]}" -eq 0 ]; then echo "ERROR: No Mach-O binaries found inside $fw" >&2 exit 1 fi - echo " Signed $signed_count Mach-O binary/binaries inside $fw" + # Sign the canonical (first) binary via temp copy to avoid the + # in-place "bundle format is ambiguous" error from codesign. + canonical="${fw_bins[0]}" + existing_id=$(codesign -d "$canonical" 2>&1 \ + | sed -n 's/^Identifier=//p' || true) + if [ -z "$existing_id" ]; then + existing_id=$(basename "$canonical") + fi + echo " Signing canonical framework binary: $canonical (identifier: $existing_id)" + tmp_binary=$(mktemp) + cp "$canonical" "$tmp_binary" + codesign --force --options runtime --timestamp \ + --entitlements "$ENTITLEMENTS" \ + --identifier "$existing_id" \ + --sign "$APPLE_PERSONALID" \ + "$tmp_binary" || { rm -f "$tmp_binary"; exit 1; } + cp "$tmp_binary" "$canonical" || { rm -f "$tmp_binary"; exit 1; } + rm -f "$tmp_binary" + # Copy the signed canonical to all duplicate paths so they share + # byte-identical signatures (Apple notarization checks all paths). + for fw_bin in "${fw_bins[@]:1}"; do + echo " Syncing signed binary to duplicate path: $fw_bin" + cp "$canonical" "$fw_bin" || exit 1 + done + echo " Signed 1 + synced $((${#fw_bins[@]} - 1)) duplicate(s) inside $fw" else echo "ERROR: Failed to sign $fw: $sign_output" >&2 exit 1 From b2ed94263489e22d19f1ce6417fd7c1d638c9858 Mon Sep 17 00:00:00 2001 From: Bob Date: Thu, 9 Apr 2026 21:23:38 +0000 Subject: [PATCH 2/2] fix(build): guard cmp-s before syncing duplicates, use cp -p for temp copy - Use cp -p when copying canonical to temp file to preserve execute bits, making the behavior explicit rather than relying on destination inode mode. - Add cmp -s guard before overwriting each non-canonical path: if a binary differs from the canonical, sign it separately via temp copy instead of silently replacing it. This prevents silent corruption if a framework with genuinely distinct Mach-O files triggers the ambiguous-bundle fallback. Addresses Greptile P2 review findings on PR #1256. --- scripts/package/build_app_tauri.sh | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/scripts/package/build_app_tauri.sh b/scripts/package/build_app_tauri.sh index 9cbe2d70e..6af17c241 100755 --- a/scripts/package/build_app_tauri.sh +++ b/scripts/package/build_app_tauri.sh @@ -185,7 +185,7 @@ if [ -n "$APPLE_PERSONALID" ]; then fi echo " Signing canonical framework binary: $canonical (identifier: $existing_id)" tmp_binary=$(mktemp) - cp "$canonical" "$tmp_binary" + cp -p "$canonical" "$tmp_binary" codesign --force --options runtime --timestamp \ --entitlements "$ENTITLEMENTS" \ --identifier "$existing_id" \ @@ -195,9 +195,26 @@ if [ -n "$APPLE_PERSONALID" ]; then rm -f "$tmp_binary" # Copy the signed canonical to all duplicate paths so they share # byte-identical signatures (Apple notarization checks all paths). + # Guard with cmp -s so genuinely distinct binaries are signed + # separately rather than silently overwritten. for fw_bin in "${fw_bins[@]:1}"; do - echo " Syncing signed binary to duplicate path: $fw_bin" - cp "$canonical" "$fw_bin" || exit 1 + if cmp -s "$canonical" "$fw_bin"; then + echo " Syncing signed binary to duplicate path: $fw_bin" + cp "$canonical" "$fw_bin" || exit 1 + else + echo " WARNING: $fw_bin differs from canonical; signing separately" >&2 + tmp2=$(mktemp) + fw_id=$(codesign -d "$fw_bin" 2>&1 | sed -n 's/^Identifier=//p' || true) + [ -z "$fw_id" ] && fw_id=$(basename "$fw_bin") + cp -p "$fw_bin" "$tmp2" + codesign --force --options runtime --timestamp \ + --entitlements "$ENTITLEMENTS" \ + --identifier "$fw_id" \ + --sign "$APPLE_PERSONALID" \ + "$tmp2" || { rm -f "$tmp2"; exit 1; } + cp "$tmp2" "$fw_bin" || { rm -f "$tmp2"; exit 1; } + rm -f "$tmp2" + fi done echo " Signed 1 + synced $((${#fw_bins[@]} - 1)) duplicate(s) inside $fw" else