fix: preserve entitlements and remove --deep codesign to fix local network permission on reboot

On macOS 26.3, the local network permission was silently lost on every reboot.
Diagnostics confirmed that `codesign --deep --force` was stripping entitlements
from the main binary, and `nehelper` (Network Extension Helper) could not resolve
the process identity on cold boot, causing mDNS connections to be silently blocked.

Changes:
- Replace `--deep` codesign with explicit inside-out signing that preserves entitlements
- Add `disable-library-validation` and `allow-unsigned-executable-memory` entitlements
  to both the main app and PyInstaller executables
- Sign PyInstaller executables separately from libraries, with entitlements
- Add post-sign verification step in CI

Co-Authored-By: Claude Opus 4.6 <[email protected]>
This commit is contained in:
Alex Cheema
2026-03-06 01:07:19 -08:00
co-authored by Claude Opus 4.6
parent b9d40e8e35
commit e90289635e
3 changed files with 48 additions and 2 deletions
+34 -2
View File
@@ -285,12 +285,23 @@ jobs:
security unlock-keychain -p "$MACOS_CERTIFICATE_PASSWORD" "$BUILD_KEYCHAIN_PATH"
SIGNING_IDENTITY=$(security find-identity -v -p codesigning "$BUILD_KEYCHAIN_PATH" | awk -F '"' '{print $2}')
RUNTIME_DIR="EXO.app/Contents/Resources/exo"
find "$RUNTIME_DIR" -type f \( -perm -111 -o -name "*.dylib" -o -name "*.so" \) -print0 |
RUNTIME_ENTITLEMENTS="$GITHUB_WORKSPACE/packaging/entitlements/runtime.entitlements"
# Sign .so and .dylib files first (no entitlements needed for libraries)
find "$RUNTIME_DIR" -type f \( -name "*.dylib" -o -name "*.so" \) -print0 |
while IFS= read -r -d '' file; do
/usr/bin/codesign --force --timestamp --options runtime \
--sign "$SIGNING_IDENTITY" "$file"
done
# Sign executables with entitlements (they load libraries via dlopen)
find "$RUNTIME_DIR" -type f -perm -111 ! -name "*.dylib" ! -name "*.so" -print0 |
while IFS= read -r -d '' file; do
/usr/bin/codesign --force --timestamp --options runtime \
--entitlements "$RUNTIME_ENTITLEMENTS" \
--sign "$SIGNING_IDENTITY" "$file"
done
- name: Sign, notarize, and create DMG
env:
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
@@ -301,8 +312,29 @@ jobs:
cd output
security unlock-keychain -p "$MACOS_CERTIFICATE_PASSWORD" "$BUILD_KEYCHAIN_PATH"
SIGNING_IDENTITY=$(security find-identity -v -p codesigning "$BUILD_KEYCHAIN_PATH" | awk -F '"' '{print $2}')
/usr/bin/codesign --deep --force --timestamp --options runtime \
APP_ENTITLEMENTS="$GITHUB_WORKSPACE/app/EXO/EXO/EXO.entitlements"
# Sign frameworks (Sparkle) inside-out
find EXO.app/Contents/Frameworks -name "*.framework" -print0 | \
while IFS= read -r -d '' framework; do
/usr/bin/codesign --force --timestamp --options runtime \
--sign "$SIGNING_IDENTITY" "$framework"
done
# Sign the main app binary with entitlements (no --deep!)
/usr/bin/codesign --force --timestamp --options runtime \
--entitlements "$APP_ENTITLEMENTS" \
--sign "$SIGNING_IDENTITY" EXO.app/Contents/MacOS/EXO
# Sign the outer app bundle with entitlements
/usr/bin/codesign --force --timestamp --options runtime \
--entitlements "$APP_ENTITLEMENTS" \
--sign "$SIGNING_IDENTITY" EXO.app
# Verify the complete signature chain
/usr/bin/codesign --verify --deep --strict --verbose=2 EXO.app
echo "Verifying entitlements are embedded:"
/usr/bin/codesign -d --entitlements - EXO.app/Contents/MacOS/EXO
mkdir -p dmg-root
cp -R EXO.app dmg-root/
ln -s /Applications dmg-root/Applications
+4
View File
@@ -8,5 +8,9 @@
<true/>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
</dict>
</plist>
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
</dict>
</plist>