Helper app is sandboxed (entitlement + runtime check), but `URLsForDirectory:` returns user home (`/Users//`) instead of container path — why?

Problem summary

I have a macOS helper app that is launched from a sandboxed main app. The helper:

  • has com.apple.security.app-sandbox = true and com.apple.security.inherit = true in 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) returns 1 (runtime sandbox enforcement present),
  • APP_SANDBOX_CONTAINER_ID environment 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:] and URLForDirectory: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() and URLsForDirectory: are made after main() to avoid "too early" initialization problems.
  • Helper is placed in Contents/MacOS (not Contents/Library/LoginItems).
  • Helper is a non-GUI helper binary launched by the main app (not an XPC service).
  • macOS version: Sequoia 15.6

Questions

  1. Why do NSHomeDirectory() and URLsForDirectory: return the real /Users/<me>/... paths in a helper process that is clearly sandboxed (entitlement + runtime enforcement), while NSItemReplacementDirectory returns a container-scoped temporary path?

  2. Is this behavior related to how the helper is packaged or launched (e.g., placement in Contents/MacOS vs Contents/Library/LoginItems, or whether it is launched with posix_spawn/fork+exec vs other APIs)?

  3. 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

How are you starting this helper process? Via NSTask (Process in Swift)? Or posix_spawn? Or something else?

Does the helper have its own bundle structure. So, something like this:

MyApp.app/
    Contents/
        Info.plist
        MacOS/
            MyApp
            MyHelper.app/
                Contents/
                    Info.plist
                    MacOS/
                        MyHelper

Or is it a standalone executable, like this:

MyApp.app/
    Contents/
        Info.plist
        MacOS/
            MyApp
            MyHelper

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Helper app is sandboxed (entitlement &#43; runtime check), but &#96;URLsForDirectory:&#96; returns user home (&#96;/Users//&#96;) instead of container path — why?
 
 
Q