Problem summary
I have a macOS helper app that is launched from a sandboxed main app. The helper:
- has
com.apple.security.app-sandbox = trueandcom.apple.security.inherit = truein its entitlements, - is signed and embedded inside the main app bundle (placed next to the main executable in
Contents/MacOS), - reports
entitlement_check = 1(code signature contains sandbox entitlement, implemented via SecStaticCode… check), sandbox_check(getpid(), NULL, 0)returns1(runtime sandbox enforcement present),APP_SANDBOX_CONTAINER_IDenvironment variable is not present (0).
Despite that, Cocoa APIs return non-container home paths:
NSHomeDirectory()returns/Users/<me>/(the real home).[[NSFileManager defaultManager] URLsForDirectory:inDomains:]andURLForDirectory:inDomain:appropriateForURL:create:error:return paths rooted at/Users/<me>/(not under~/Library/Containers/<app_id>/Data/...) — i.e. they look like non-sandboxed locations.
However, one important exception: URLForDirectory:... for NSItemReplacementDirectory (temporary/replacement items) does return a path under the helper's container (example: ~/Library/Containers/<app_id>/Data/tmp/TemporaryItems/NSIRD_<helper_name>_hfc1bZ).
This proves the sandbox is active for some FileManager APIs, yet standard directory lookups (Application Support, Documents, Caches, and NSHomeDirectory()) are not being redirected to the container.
What I expect
The helper (which inherits the sandbox and is clearly sandboxed) should get container-scoped paths from Cocoa’s FileManager APIs (Application Support, Documents, Caches), i.e. paths under the helper’s container: /Users/<me>/Library/Containers/<app_id>/Data/....
What I tried / diagnostics already gathered
Entitlements & code signature
codesign -d --entitlements :- /path/to/Helper.app/Contents/MacOS/Helper
# shows com.apple.security.app-sandbox = true and com.apple.security.inherit = true
Runtime checks (Objective-C++ inside helper):
extern "C" int sandbox_check(pid_t pid, const char *op, int flags);
NSLog(@"entitlement_check = %d", entitlement_check()); // SecStaticCode check
NSLog(@"env_variable_check = %d", (getenv("APP_SANDBOX_CONTAINER_ID") != NULL));
NSLog(@"runtime_sandbox_check = %d", sandbox_check(getpid(), nullptr, 0));
NSLog(@"NSHomeDirectory = %s", NSHomeDirectory());
NSArray *urls = [[NSFileManager defaultManager]
URLsForDirectory:NSApplicationSupportDirectory
inDomains:NSUserDomainMask];
NSLog(@"URLsForDirectory: %@", urls);
Observed output:
entitlement_check = 1
env_variable_check = 0
runtime_sandbox_check = 1
NSHomeDirectory = /Users/<me>
URLsForDirectory: ( "file:///Users/<me>/Library/Application%20Support/..." )
Temporary/replacement directory (evidence sandbox active for some APIs):
NSURL *tmpReplacement = [[NSFileManager defaultManager]
URLForDirectory:NSItemReplacementDirectory
inDomain:NSUserDomainMask
appropriateForURL:nil
create:YES
error:&err];
NSLog(@"NSItemReplacementDirectory: %@", tmpReplacement.path);
Observed output (example):
/Users/<me>/Library/Containers/<app_id>/Data/tmp/TemporaryItems/NSIRD_<helper_name>_hfc1bZ
Other facts
- Calls to
NSHomeDirectory()andURLsForDirectory:are made aftermain()to avoid "too early" initialization problems. - Helper is placed in
Contents/MacOS(notContents/Library/LoginItems). - Helper is a non-GUI helper binary launched by the main app (not an XPC service).
- macOS version: Sequoia 15.6
Questions
-
Why do
NSHomeDirectory()andURLsForDirectory:return the real/Users/<me>/...paths in a helper process that is clearly sandboxed (entitlement + runtime enforcement), whileNSItemReplacementDirectoryreturns a container-scoped temporary path? -
Is this behavior related to how the helper is packaged or launched (e.g., placement in
Contents/MacOSvsContents/Library/LoginItems, or whether it is launched withposix_spawn/fork+execvs other APIs)? -
Are there additional entitlements or packaging rules required for a helper that inherits sandbox to have Cocoa directory APIs redirected to the container (for Application Support, Documents, Caches)?
*Thanks in advance — I can add any requested logs