macOS 27 Denies Cross-Team Container Access Without Asking

On macOS, containerURL(forSecurityApplicationGroupIdentifier:) returns a valid-looking URL for an app group you have no entitlement for. Apple documents this plainly: on iOS the method returns nil for an invalid identifier, but in macOS “a URL of the expected form is always returned, even if the app group is invalid.”4 In macOS 27, that behavior collides with a new restriction.

macOS 27 stopped asking the user before denying cross-team container access. Reading files in another developer team’s app data container or app group container previously produced an authorization prompt. It now fails by default, recoverable only if the user finds the entry in Privacy & Security.1

Those two facts compose into a failure with no signal at the point of failure. No dialog appears. The method returns a URL rather than nil. The path looks exactly like the one you expected. The denial surfaces later, at the file operation, where it reads as a missing file.

TL;DR

macOS 27 removes the user-authorization prompt for accessing other teams’ app data and app group containers, and denies such access by default, with user control moved to Privacy & Security settings.1 The change is filed under System Integrity Protection as a new feature, not a bug fix. Apple’s own guidance on app group containers still describes the prompt behavior that macOS 27 removed. Because the macOS API returns a well-formed URL even for groups you cannot access, the denial appears at read time rather than at the API call. Same-team access is unaffected: the boundary is the Team ID.

What Changed

The macOS 27 release notes carry one sentence under System Integrity Protection:1

“Accessing files in other developer teams’ app data containers and app group containers no longer prompts the user for authorization; such accesses are denied by default and can be managed by the user in Privacy & Security settings.”

Radar 161835690. Two clauses, two separate changes.

The first clause removes a prompt. The second establishes denial as the default. Coverage of this change tends to lead with the denial, which is the less interesting half. Tightened defaults are routine. A vanished prompt changes the shape of the failure your users experience, and the shape of the bug report you receive.

Before, a user saw a dialog and made a decision. If they declined, your app knew something a human had chosen. Now nobody is asked. The access simply does not happen, and the only path to enabling it runs through a settings pane the user has no reason to visit unless someone tells them to.

The Prior Behavior Apple’s Docs Still Describe

The restriction sits on top of protection that arrived two releases earlier. Apple’s guide to app group containers states:2

“In macOS 15 and later, app group containers offer [System Integrity Protection] for your app’s local files even if the app doesn’t have the App Sandbox capability. These app group containers limit access by apps that aren’t in the app group. Apps not in the app group that attempt to access locations within an app group or app data container, result in a user prompt that requests their authorization.”

That page describes the prompt in the present tense. As of this writing, Apple has not updated it for macOS 27.

The practical consequence: a developer who hits this, searches the documentation, and finds the official page is told an authorization dialog will appear. It will not. They will then reasonably conclude the prompt is broken, or that their entitlements are misconfigured, and spend time in the wrong place.

Worth verifying the state of that page yourself rather than trusting this post’s timestamp. Documentation catches up.

Why the Failure Lands Late

The API behavior is the part that turns a policy change into a debugging problem.

Apple’s reference for containerURL(forSecurityApplicationGroupIdentifier:) is explicit about the platform split:4

“A URL indicating the location of the group’s shared directory in the file system. In iOS, the value is nil when the group identifier is invalid. In macOS, a URL of the expected form is always returned, even if the app group is invalid, so be sure to test that you can access the underlying directory before attempting to use it.”

The discussion section repeats the warning: calling the method with a group identifier you hold no entitlement for still returns a URL of the expected form, but the directory does not exist and a sandboxed app cannot create it.4

So the common defensive pattern does nothing here:

// This guard passes on macOS regardless of entitlement.
guard let url = FileManager.default.containerURL(
    forSecurityApplicationGroupIdentifier: "ABCDE12345.com.example.shared"
) else {
    return  // never taken on macOS
}

The URL is well-formed. It points at ~/Library/Group Containers/<team>.<group>, which is exactly where that container would live. Everything reads as success until a file operation runs against it.

The fix is to attempt the read and handle the failure, rather than asking whether it would succeed:

let fm = FileManager.default
guard let url = fm.containerURL(
    forSecurityApplicationGroupIdentifier: groupID
) else { return }  // macOS never takes this branch

do {
    _ = try fm.contentsOfDirectory(atPath: url.path)
    // Reached the container.
} catch {
    // On macOS 27 a cross-team denial lands here.
    // No prompt fired. The user was never asked.
    presentContainerUnavailable(error)
}

Resist the temptation to reach for isReadableFile(atPath:) as a pre-check. Apple advises against that whole class of test:3

“Attempting to predicate behavior based on the current state of the file system or a particular file on the file system is not recommended. Doing so can cause odd behavior or race conditions. It’s far better to attempt an operation (such as loading a file or creating a directory), check for errors, and handle those errors gracefully than it is to try to figure out ahead of time whether the operation will succeed.”

There is a second reason specific to this change. The same page notes that isReadableFile(atPath:) “uses the real user ID and group ID” to decide readability.3 That is POSIX permission evaluation. A cross-team container denial is a policy decision layered above POSIX, so a permission-bit check is not guaranteed to reflect it. A pre-check that returns true before a read that fails anyway is worse than no pre-check, because it moves the surprise further from the cause.

Readers of canOpenURL is deprecated in iOS 27 will recognize the shape. Apple keeps removing the ability to ask in advance and leaving you the ability to try. Attempt-and-handle is becoming the general answer, not a workaround for one API.

Note the asymmetry across platforms. iOS returns nil and fails honestly at the call site. macOS returns a URL and defers the failure. The macOS 27 change removes the prompt, which was the one remaining signal on the platform that was already the quieter of the two.

One more trap: Apple’s guidance says to always use the URL this method returns rather than building ~/Library/Group Containers/... by hand, because the location may change in future releases.4 Anyone who hardcoded that path has no method call to check at all, and no place to put the readability test.

Probably Not SDK-Gated

A natural question is whether staying on an older SDK defers the change. The release notes do not say.

What they do show is a pattern. The macOS 27 notes use explicit SDK-gating language in 11 separate entries: “In apps built with the macOS 27.0 SDK,” “In apps built with the 27.0 SDKs,” “When your project has a minimum deployment target lower than 27.0.”1 Apple qualifies these changes when the qualification applies.

The container entry carries no such qualifier. On the reasonable reading that the absence is deliberate, the restriction is an OS-level policy that applies to every binary running on macOS 27 regardless of which SDK built it.

Treat that as a strong inference and not a statement of fact, because Apple has not said it directly. The operational consequence either way: do not plan on an older SDK as a mitigation. Plan on the access failing.

Who This Actually Hits

Same-team access is untouched. Apple’s rules make the Team ID boundary structural rather than incidental: “Different developer teams can’t use the same app group,” while one team can share a group across its own apps and supporting processes.2

The apps that break are the ones reaching across that line:

Migration and import tools. Anything that reads a competitor’s or predecessor’s container to import user data. This is the clearest case, and it was already gated behind a prompt that users sometimes approved.

Backup and sync utilities. Tools that enumerate app containers to back them up now silently skip anything outside their own team.

Companion apps that changed hands. The sharpest case, because nothing about the code changes. Two apps ship under one Team ID and share a group happily. An acquisition, a team split, or a move to a separate developer account puts them under different Team IDs. The app group identifier still looks right, the URL still resolves, and the data stops arriving.

That last case deserves dwelling on, because the timing works against you. Team ID changes are handled by whoever manages signing and distribution, and the shared-container dependency is usually invisible from that vantage point. The code compiles. Both apps ship. Neither test suite catches it, because unit tests do not exercise a real cross-team container and CI runs both targets under whatever identity the build machine holds. The failure appears in production, on user machines, as data that stopped syncing between two apps that a user reasonably considers the same product.

If you are planning a Team ID migration, the audit is mechanical: grep for every app group identifier your targets declare, and for each one, list which bundles read it. Any identifier read by bundles that will land under different Team IDs is a break. That is a ten-minute check before the move and a support incident after it.

Apple lists what can participate in app groups: main executables in bundle structures, app extensions, App Clips, and XPC Services.2 Each is a place this can surface.

Diagnosing It

Two checks are worth running before assuming a code bug.

Confirm the system validated your entitlements at all. Apple documents a runtime check for the entitlements-validated flag on a running process:2

sudo launchctl procinfo <pid>

Provisioning profiles created before app-group authorization existed may not carry it, which produces access failures that look identical to the macOS 27 denial but have a different cause. Xcode refreshes profiles when “Automatically manage signing” is on and the REGISTER_APP_GROUPS build setting is set to Yes.2

Know which identifier style you are using. Groups prefixed with group. must be included in the app’s provisioning profile. Groups using the <TeamID>.<group name> form need no profile, because the system verifies the team identifier prefix against the signing identity, but that form is macOS-only and unsupported by Keychain Access Groups.2

Then check Privacy & Security, since that is where the release note says user control now lives.1

Designing for a Denial You Cannot Prompt For

The prompt was doing product work, not just security work. It told the user a decision existed, and it told your app when one had been made. Both jobs now fall to you.

Probe at the boundary, once. Attempt one cheap read when you first resolve the container, rather than discovering the denial partway through a sync loop. A failure deep in a queue produces a partial result and an error attributed to whichever file happened to be next. One attempt at the point of resolution gives you a single place to branch, and unlike a permission pre-check it exercises the same path your real reads will take.

Say what happened, in the user’s terms. “Could not read data” invites a support ticket about data loss. The accurate message names the boundary and the remedy: the data belongs to an app from a different developer, macOS blocks that access by default, and the switch lives in Privacy & Security. Users cannot act on a failure they cannot locate.

Do not retry. A denial is a policy state, not a transient error. Backoff loops against a blocked container burn battery and fill logs while changing nothing. Fail once, surface the state, and offer a recheck the user can trigger after changing the setting.

Recheck on activation rather than polling. When the user leaves to change a setting and returns, that is the moment to test access again. Polling a blocked path on a timer produces the same denial at whatever interval you picked.

Ask whether you need the cross-team read at all. This is the uncomfortable one, and often the right answer. A migration tool that reads a competitor’s container is doing something the platform has been narrowing for three releases: sandboxed in macOS 15, prompted, and now denied. An export path the other app controls, a document-based import through a file picker the user drives, or a documented interchange format all survive this change. Container reads across a Team ID boundary are on a trajectory, and the trajectory points one way.

The file picker deserves specific mention, because it is the escape hatch the platform intends. A user selecting a file grants access explicitly through the security-scoped machinery, which is a stronger permission story than the prompt that just disappeared, and it works today without any setting change.

Key Takeaways

For macOS app developers: - Never treat a non-nil return from containerURL(forSecurityApplicationGroupIdentifier:) as proof of access on macOS. It always returns a URL. Attempt a read and handle the error. - Skip isReadableFile(atPath:) as a pre-check. Apple advises against predicting file-system outcomes, and it evaluates POSIX permissions, which a policy-layer denial need not touch. - Audit any code path that reads a container belonging to a different Team ID. On macOS 27 it fails without asking anyone. - If you hardcoded ~/Library/Group Containers/..., you have no method call to guard. Move to the API so there is somewhere to put the check.

For teams shipping migration or backup tools: - Cross-team reads are no longer a prompt away. Design for denial as the default state and tell users where the setting lives. - The failure presents as absent data, not as an error. Add explicit messaging, because users will otherwise report it as data loss.

For anyone changing Team IDs: - An acquisition or account split silently severs app group sharing between apps that previously shared a Team ID prefix. Nothing in the source code signals it.

FAQ

Does this affect apps that share a container within one developer account?

No. The boundary is the Team ID. Apple’s rules already prevent different teams from using the same app group, and one team can share a group across its own apps, extensions, App Clips, and XPC Services.2

Will my users see a prompt they can approve?

Not on macOS 27. The release note states that these accesses no longer prompt for authorization and are denied by default, with management moved to Privacy & Security settings.1

Can I avoid it by building against an older SDK?

Probably not. The release notes use explicit SDK-gating language for 11 other changes and none for this one, which suggests an OS-level policy applying to all binaries.1 Apple has not stated it outright, so treat it as a strong inference and plan for the access to fail.

How do I tell this apart from a provisioning problem?

Run sudo launchctl procinfo <pid> and check whether the system set the entitlements-validated flag on your process. Older provisioning profiles may predate app-group authorization and produce a similar-looking failure with an unrelated cause.2

Why does the documentation still describe a prompt?

Apple’s app group containers guide describes the macOS 15 behavior and had not been updated for the macOS 27 change as of this writing.2 Verify the current state of that page rather than relying on this post’s date.

Sources


  1. Apple, “macOS 27 Golden Gate Beta 4 Release Notes.” System Integrity Protection, New Features: “Accessing files in other developer teams’ app data containers and app group containers no longer prompts the user for authorization; such accesses are denied by default and can be managed by the user in Privacy & Security settings.” Radar 161835690. Also the source for the SDK-gating language used in 11 other entries and absent from this one. The HTML renders client-side; the machine-readable copy is at developer.apple.com/tutorials/data/documentation/macos-release-notes/macos-27-release-notes.json

  2. Apple, “Accessing app group containers in your existing macOS app.” Source for the macOS 15 prompt behavior, the rule that different developer teams cannot share an app group, the group. versus <TeamID>.<group name> identifier distinction, the sudo launchctl procinfo entitlements-validated check, the REGISTER_APP_GROUPS build setting, and the list of eligible container participants. Retrieved 2026-08-01, still describing the prompt. 

  3. Apple, “isReadableFile(atPath:).” Source for the guidance against predicting file-system state: “It’s far better to attempt an operation (such as loading a file or creating a directory), check for errors, and handle those errors gracefully than it is to try to figure out ahead of time whether the operation will succeed.” Also the source for the note that the method “uses the real user ID and group ID, as opposed to the effective user and group IDs, to determine if the file is readable,” which is why a POSIX-level check is not a reliable proxy for a policy-layer denial. 

  4. Apple, “containerURL(forSecurityApplicationGroupIdentifier:).” Return value: “In iOS, the value is nil when the group identifier is invalid. In macOS, a URL of the expected form is always returned, even if the app group is invalid, so be sure to test that you can access the underlying directory before attempting to use it.” Also the source for the guidance against constructing the container path manually. 

Related Posts

Apple's Font Interpreter Is Now Swift, and 13% Faster

Apple's security team rewrote the TrueType hinting interpreter from C to memory-safe Swift, made it 13% faster, open-sou…

9 min read

What's New in SwiftUI for iOS 27

iOS 27 reworks SwiftUI lists, documents, toolbars, and errors: drag-to-reorder, a readable/writable document model, tool…

27 min read

The Mac App Store Won't Let Window Managers Exist. I Shipped One Anyway.

App Sandbox forbids the API every window tiler needs, and Apple said ship outside the store. 941 Tiles ships inside it: …

8 min read