From 4fc30562fb0428d3d6f03850870561f9b6115ae4 Mon Sep 17 00:00:00 2001 From: Jose Olarte III Date: Thu, 25 Jun 2026 17:48:53 +0800 Subject: [PATCH] docs(ios): add App Group configuration audit for share extension Document App/extension entitlements, bundle IDs, teams, and signing, flagging a code-vs-entitlement App Group ID mismatch (code uses group.app.timesafari.share while entitlements grant group.app.trentlarson.timesafari.share) that breaks shared storage. --- doc/share-extension-app-group-audit.md | 155 +++++++++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 doc/share-extension-app-group-audit.md diff --git a/doc/share-extension-app-group-audit.md b/doc/share-extension-app-group-audit.md new file mode 100644 index 00000000..fe7518fe --- /dev/null +++ b/doc/share-extension-app-group-audit.md @@ -0,0 +1,155 @@ +# iOS App Group Configuration Audit + +**Generated:** 2026-06-25 17:31:15 PST + +## Scope + +Static inspection of App Group configuration for the **App** target and the **TimeSafariShareExtension** target: entitlements, capabilities, bundle identifiers, Debug/Release build settings, and signing. No code was modified. + +### Files Inspected + +| File | Role | +|------|------| +| `ios/App/App/App.entitlements` | App target App Group declaration | +| `ios/App/TimeSafariShareExtension/TimeSafariShareExtension.entitlements` | Extension App Group declaration | +| `ios/App/App.xcodeproj/project.pbxproj` | Bundle IDs, teams, signing, entitlement linkage | +| `ios/App/App/SharedImageUtility.swift` | App Group identifier used by main app code | +| `ios/App/TimeSafariShareExtension/ShareViewController.swift` | App Group identifier used by extension code | + +--- + +## CRITICAL FINDING — Code vs Entitlements App Group Mismatch + +The entitlements and the Swift source declare **different** App Group identifiers: + +| Location | App Group identifier | +|----------|----------------------| +| `App.entitlements` | `group.app.trentlarson.timesafari.share` | +| `TimeSafariShareExtension.entitlements` | `group.app.trentlarson.timesafari.share` | +| `SharedImageUtility.swift` (`appGroupIdentifier`) | `group.app.timesafari.share` | +| `ShareViewController.swift` (`appGroupIdentifier`) | `group.app.timesafari.share` | + +The runtime code targets `group.app.timesafari.share`, but **neither target is entitled to that group** — both entitlements now grant `group.app.trentlarson.timesafari.share`. + +This is an **uncommitted change**: `git diff` shows both entitlements were just changed from `group.app.timesafari.share` → `group.app.trentlarson.timesafari.share`, while the Swift code still uses the old value. Before this edit the code and entitlements matched; after it they do not. + +### Runtime Consequences + +- `FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.app.timesafari.share")` returns **nil** (the app is not entitled to that group). The extension's `storeImageData` aborts via `guard let containerURL` → image file is never written; the main app's reads return nil. +- `UserDefaults(suiteName: "group.app.timesafari.share")` does **not** resolve to the shared, entitled suite. Writes fall back to each process's own preferences domain, so the extension's keys (`sharedPhotoFilePath`, `sharedPhotoShareId`, `shareExtensionLastStart`, `sharedPhotoReady`) are **not visible** to the main app. + +Net effect: the entire share-target handoff via the App Group breaks while this mismatch exists. This is the most likely root cause of "App Group UserDefaults writes failing." + +**Note:** This affects both Debug and Release (the entitlements have no per-configuration variants), not Debug only. + +--- + +## Direct Answers + +### Do both targets declare the same App Group? + +**Yes — the two entitlements files match each other.** Both `App.entitlements` and `TimeSafariShareExtension.entitlements` declare exactly: + +```xml +com.apple.security.application-groups + + group.app.trentlarson.timesafari.share + +``` + +However, **the code does not match the entitlements** (see Critical Finding). So "same App Group" is true at the entitlement level, false at the entitlement-vs-code level. + +### Are there any Debug vs Release differences? + +**Entitlements / App Group:** No. A single entitlements file per target applies to both configurations; the App Group string is identical in Debug and Release (`group.app.trentlarson.timesafari.share`). + +**Bundle identifiers:** Yes — they differ by configuration: + +| Target | Debug | Release | +|--------|-------|---------| +| App | `app.trentlarson.timesafari` | `app.timesafari` | +| Extension | `app.trentlarson.timesafari.TimeSafariShareExtension` | `app.timesafari.TimeSafariShareExtension` | + +(The Debug bundle IDs were just changed from the `app.timesafari*` form per `git diff`.) + +**Development team:** Yes — differs by configuration (see next answer). + +In both configurations the extension bundle ID is correctly nested under the app bundle ID, which is required for an app extension. + +### Are there any team-ID differences that could affect App Group access? + +| Configuration | App team | Extension team | Match? | +|---------------|----------|----------------|--------| +| Debug | `7XVXYPEQYJ` | `7XVXYPEQYJ` | ✅ same | +| Release | `GM3FS5JQPH` | `GM3FS5JQPH` | ✅ same | + +- **Within each configuration, both targets use the same team** — this is the condition required for two targets to share an App Group, and it is satisfied. +- **Across configurations the teams differ** (Debug `7XVXYPEQYJ` vs Release `GM3FS5JQPH`). The Debug team was just changed from `GM3FS5JQPH` per `git diff`. + +Implications: +1. The App Group container is namespaced by Team ID at runtime (`$(TeamID).group...`). A Debug install (team `7XVXYPEQYJ`) and a Release install (team `GM3FS5JQPH`) use **different physical containers** and cannot share data with each other. This is normal and only matters if you expect data continuity between Debug and Release builds. +2. With **Automatic** signing, the App Group `group.app.trentlarson.timesafari.share` must be registered/enabled for **both** teams. If it is not provisioned under the Debug team `7XVXYPEQYJ`, automatic signing of the Debug build can fail to include the App Group entitlement (or fail to sign), which would also break App Group access in Debug. + +### Are there signing/entitlement mismatches that could cause App Group UserDefaults writes to fail in Debug builds? + +**Yes.** In order of severity: + +1. **(Primary) Code/entitlement group-ID mismatch.** Code uses `group.app.timesafari.share`; entitlements grant `group.app.trentlarson.timesafari.share`. The code's group is not entitled, so shared `UserDefaults`/container access fails. Affects Debug and Release. + +2. **(Debug-specific risk) App Group provisioning under the Debug team.** Debug now signs with team `7XVXYPEQYJ` (changed from `GM3FS5JQPH`). Under Automatic signing, if `group.app.trentlarson.timesafari.share` is not enabled for team `7XVXYPEQYJ`, the Debug build's App Group entitlement may not be granted, causing writes to silently fall back to the local domain. + +3. **(Consistency) Bundle-ID change accompanying the team change.** Debug bundle IDs changed to `app.trentlarson.timesafari*`. App Groups don't have to match bundle IDs, so this is not a direct cause, but combined with the new team it means Debug provisioning is a distinct profile/identifier set that must independently carry the App Group capability. + +No mismatch was found **between the two entitlement files themselves**, and no per-configuration entitlement override exists. + +--- + +## Detailed Configuration + +### Entitlements (identical content in both files) + +```xml +com.apple.security.application-groups + + group.app.trentlarson.timesafari.share + +``` + +`CODE_SIGN_ENTITLEMENTS` linkage (both Debug and Release): + +| Target | Entitlements path | +|--------|-------------------| +| App | `App/App.entitlements` | +| Extension | `TimeSafariShareExtension/TimeSafariShareExtension.entitlements` | + +### Bundle Identifiers, Teams, Signing (project.pbxproj) + +| Setting | App Debug | App Release | Ext Debug | Ext Release | +|---------|-----------|-------------|-----------|-------------| +| `PRODUCT_BUNDLE_IDENTIFIER` | `app.trentlarson.timesafari` | `app.timesafari` | `app.trentlarson.timesafari.TimeSafariShareExtension` | `app.timesafari.TimeSafariShareExtension` | +| `DEVELOPMENT_TEAM` | `7XVXYPEQYJ` | `GM3FS5JQPH` | `7XVXYPEQYJ` | `GM3FS5JQPH` | +| `CODE_SIGN_STYLE` | Automatic | Automatic | Automatic | Automatic | +| `CODE_SIGN_ENTITLEMENTS` | `App/App.entitlements` | same | `TimeSafariShareExtension/...entitlements` | same | +| App Group (from entitlements) | `group.app.trentlarson.timesafari.share` | same | same | same | + +### App Group Identifier Used in Code + +```swift +// SharedImageUtility.swift:13 and ShareViewController.swift:13 +private let appGroupIdentifier = "group.app.timesafari.share" // ← does NOT match entitlements +``` + +--- + +## Recommendations (no code changed) + +1. **Resolve the group-ID mismatch.** Either revert the entitlements back to `group.app.timesafari.share`, or update the two Swift constants to `group.app.trentlarson.timesafari.share`. Both sides must use one identical string. +2. **Confirm App Group provisioning per team.** Ensure `group.app.trentlarson.timesafari.share` (whichever string is chosen) is enabled for both `7XVXYPEQYJ` (Debug) and `GM3FS5JQPH` (Release) so Automatic signing includes the capability in both configurations. +3. **Decide whether the Debug↔Release team/bundle-ID split is intentional.** If cross-config data continuity is ever expected, note that different Team IDs yield different App Group containers. +4. **Verify at runtime** using the existing `getShareExtensionDiagnostics()` / `[ShareTarget]` logs: after aligning identifiers, `shareExtensionLastStart` written by the extension should become readable by the main app. + +--- + +## Conclusion + +The two **entitlement files agree** on the App Group (`group.app.trentlarson.timesafari.share`) and, **within each build configuration**, both targets share the same Development Team and consistent nested bundle IDs — the structural requirements for App Group sharing are met. The decisive problem is that the **Swift code still references the old group `group.app.timesafari.share`**, which no entitlement grants; this breaks both shared `UserDefaults` and the shared container in all builds. Secondarily, the recent Debug switch to team `7XVXYPEQYJ` means the chosen App Group must be provisioned under that team for Debug App Group access to work under Automatic signing.