From 81802a7c7272db4073b68a012a53a44e87ce9122 Mon Sep 17 00:00:00 2001 From: Huang Xin Date: Thu, 2 Jul 2026 23:22:26 +0900 Subject: [PATCH] fix(ios): keep App Group entitlement on widget/share extensions in App Store builds (#4891) The App Store export re-sign (xcodebuild -exportArchive, automatic signing) stripped com.apple.security.application-groups from the ReadestWidget and ShareExtension binaries because both targets set CODE_SIGN_ALLOW_ENTITLEMENTS_MODIFICATION: YES. The provisioning profiles and source entitlements both grant the group, but the signed extension binaries did not, so the widget read an empty snapshot from the shared App Group container and showed only the placeholder book icon. Dev builds were unaffected. Remove the flag from both extension targets (the main app already ships the group correctly without it) so signing uses the exact CODE_SIGN_ENTITLEMENTS content. Add scripts/verify-ios-appstore-entitlements.sh and run it from release-ios-appstore.sh before upload so a stripped App Group fails the release instead of shipping a dead widget. Co-authored-by: Claude Opus 4.8 (1M context) --- .../scripts/release-ios-appstore.sh | 4 ++ .../verify-ios-appstore-entitlements.sh | 49 +++++++++++++++++++ .../src-tauri/gen/apple/project.yml | 25 +++++----- 3 files changed, 67 insertions(+), 11 deletions(-) create mode 100755 apps/readest-app/scripts/verify-ios-appstore-entitlements.sh diff --git a/apps/readest-app/scripts/release-ios-appstore.sh b/apps/readest-app/scripts/release-ios-appstore.sh index 2b28e0a3..62f42182 100644 --- a/apps/readest-app/scripts/release-ios-appstore.sh +++ b/apps/readest-app/scripts/release-ios-appstore.sh @@ -3,6 +3,10 @@ pnpm tauri ios build --export-method app-store-connect BUNDLE_DIR=src-tauri/gen/apple/build/arm64 IPA_BUNDLE=$BUNDLE_DIR/Readest.ipa +# Guard: the App Store export re-sign must not strip the App Group entitlement +# from the widget / share extensions, or the reading widget ships dead. +bash scripts/verify-ios-appstore-entitlements.sh "$IPA_BUNDLE" + xcrun altool --upload-app --type ios --file $IPA_BUNDLE --apiKey $APPLE_API_KEY --apiIssuer $APPLE_API_ISSUER echo "iOS build uploaded to App Store Connect." diff --git a/apps/readest-app/scripts/verify-ios-appstore-entitlements.sh b/apps/readest-app/scripts/verify-ios-appstore-entitlements.sh new file mode 100755 index 00000000..42169196 --- /dev/null +++ b/apps/readest-app/scripts/verify-ios-appstore-entitlements.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +# Verify the App Store IPA's app extensions carry the App Group entitlement. +# +# group.com.bilingify.readest is how the main app hands the reading-widget +# snapshot (and the share extension its shared state) to its extensions via the +# shared App Group container. Automatic App Store signing re-signs embedded +# extensions during `xcodebuild -exportArchive`; if a target sets +# CODE_SIGN_ALLOW_ENTITLEMENTS_MODIFICATION=YES, that re-sign silently strips +# com.apple.security.application-groups from the extension binary even though +# the provisioning profile and source .entitlements both grant it. The widget +# then reads an empty snapshot and shows only a placeholder book icon, with no +# build error. This guard fails the release before upload so it can't ship +# broken again. +set -euo pipefail + +IPA="${1:-src-tauri/gen/apple/build/arm64/Readest.ipa}" +GROUP="group.com.bilingify.readest" +EXTS=(ReadestWidget ShareExtension) + +if [ ! -f "$IPA" ]; then + echo "verify-ios-appstore-entitlements: IPA not found at $IPA" >&2 + exit 1 +fi + +WORK="$(mktemp -d)" +trap 'rm -rf "$WORK"' EXIT +unzip -q "$IPA" -d "$WORK" +APP="$WORK/Payload/Readest.app" + +fail=0 +for ext in "${EXTS[@]}"; do + appex="$APP/PlugIns/$ext.appex" + if codesign -d --entitlements :- "$appex" 2>/dev/null | grep -q "$GROUP"; then + echo "OK $ext.appex carries $GROUP" + else + echo "FAIL $ext.appex is MISSING $GROUP (App Group access broken)" + fail=1 + fi +done + +if [ "$fail" -ne 0 ]; then + echo "" >&2 + echo "App Group entitlement missing from an extension binary. Do NOT set" >&2 + echo "CODE_SIGN_ALLOW_ENTITLEMENTS_MODIFICATION on extension targets in" >&2 + echo "src-tauri/gen/apple/project.yml." >&2 + exit 1 +fi + +echo "All app extensions carry the App Group entitlement." diff --git a/apps/readest-app/src-tauri/gen/apple/project.yml b/apps/readest-app/src-tauri/gen/apple/project.yml index 04bbd4ac..761d09f7 100644 --- a/apps/readest-app/src-tauri/gen/apple/project.yml +++ b/apps/readest-app/src-tauri/gen/apple/project.yml @@ -153,16 +153,16 @@ targets: PRODUCT_BUNDLE_IDENTIFIER: com.bilingify.readest.ShareExtension DEVELOPMENT_TEAM: J5W48D69VR CODE_SIGN_ENTITLEMENTS: ShareExtension/ShareExtension.entitlements - # Permit Xcode's productPackagingUtility to write the auto-derived - # signing entitlements (application-identifier, team-identifier, - # get-task-allow) back to the source entitlements file at build - # time. Without this, Xcode 16+ refuses the modification it - # itself just performed and the build fails with: - # "Entitlements file ... was modified during the build" - # Safe for this target because the entitlements file is empty — - # the only modifications Xcode performs are the team / app-id - # auto-population every iOS extension target needs anyway. - CODE_SIGN_ALLOW_ENTITLEMENTS_MODIFICATION: YES + # Do NOT set CODE_SIGN_ALLOW_ENTITLEMENTS_MODIFICATION here. This + # entitlements file declares com.apple.security.application-groups + # (group.com.bilingify.readest). With automatic App Store signing, + # that flag lets `xcodebuild -exportArchive` overwrite the extension + # entitlements with a minimal auto-derived set that DROPS the app + # group — silently breaking App Group access in App Store builds + # (dev builds are unaffected). The main app ships the group + # correctly without this flag. If Xcode ever reports "Entitlements + # file was modified during the build", fix it by adding the + # auto-derived keys to the source file, not by re-enabling this flag. SWIFT_VERSION: "5.0" IPHONEOS_DEPLOYMENT_TARGET: 15.0 ARCHS: [arm64] @@ -192,7 +192,10 @@ targets: PRODUCT_BUNDLE_IDENTIFIER: com.bilingify.readest.ReadestWidget DEVELOPMENT_TEAM: J5W48D69VR CODE_SIGN_ENTITLEMENTS: ReadestWidget/ReadestWidget.entitlements - CODE_SIGN_ALLOW_ENTITLEMENTS_MODIFICATION: YES + # CODE_SIGN_ALLOW_ENTITLEMENTS_MODIFICATION is intentionally omitted + # (see ShareExtension): with it, the App Store export re-sign strips + # com.apple.security.application-groups, so the widget reads an empty + # snapshot from the App Group and shows only the placeholder book icon. SWIFT_VERSION: "5.0" IPHONEOS_DEPLOYMENT_TARGET: 15.0 ARCHS: [arm64]