Mac App Sandbox - Cannot Access Home Folder Contents

Hello:
I'm currently working on my first app which I'm looking at releasing throught the Mac App Store - but I'm not sure if what I'm trying to do is not allowed, or if I'm just being ******.

My app is a helper for another manufacturer's app, and will need to write to the other app's ~/Library/Application Support folder - so I have:

  • Enabled the App Sandbox in my application target;
  • Edited the entitlements file to add an array named 'com.apple.security.temporary-exception.files.home-relative-path.read-write', which in turn has a single string entry of '/Library/Application Support/'.


Yet, when I attempt to get the Application Support folder URL with


let applicationSupportURL = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first


then I get this returned: file:///Users/<username>/Library/Containers/<appBundleID>/Data/Library/Application%20Support/


(Note: once I get this working with the Application Support folder, then I'll further narrow it down to just the required folder I need.)



Attempting to get the user to select the appropriate folder via an OpenPanel (and thus address it via a security-scoped URL) is not desirable:

  • It's the same folder on every system that my app addresses, so why should I have to ask for it?
  • As it's in ~/Library, it's difficult to get users to reliably find that folder, considering it's usually hidden.



What am I missing?



Thanks.

Accepted Reply

First up, let’s answer your technical question. It’s true that

FileManager.urls(for:in:)
returns URLs relative to your app’s container rather than the user’s real home directory. This is generally the right thing to do, because most apps can’t access the real home directory. If you want the path to the real home directory, you’ll have to get it via other means. The approach I generally use involves
getpwuid
. For example:
func realHomeDirectory() -> URL? {
    guard let pw = getpwuid(getuid()) else { return nil }
    return URL(fileURLWithFileSystemRepresentation: pw.pointee.pw_dir, isDirectory: true, relativeTo: nil)
}

Next, let’s talk about App Review. To start, be aware that I don’t work for App Review, and thus I can’t make definitive statements on their behalf. However, my understanding is that App Review carefully audits any use of file access temporary exceptions. I’m skeptical that you’ll be able to convince them that your exception is justified.

You wrote:

It's the same folder on every system that my app addresses, so why should I have to ask for it?

Because the act of selecting that directory is considered to be explicit authorisation by the user for your app to access that directory. One of the key legs of Apple’s privacy stance is transparency, and implicit access like the one you’re proposing is not in any way transparent.

As it's in

~/Library
, it's difficult to get users to reliably find that folder, considering it's usually hidden.

True. I think you can help with this by setting the default directory of the open panel appropriately, based on the real home directory you calculated above.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Replies

First up, let’s answer your technical question. It’s true that

FileManager.urls(for:in:)
returns URLs relative to your app’s container rather than the user’s real home directory. This is generally the right thing to do, because most apps can’t access the real home directory. If you want the path to the real home directory, you’ll have to get it via other means. The approach I generally use involves
getpwuid
. For example:
func realHomeDirectory() -> URL? {
    guard let pw = getpwuid(getuid()) else { return nil }
    return URL(fileURLWithFileSystemRepresentation: pw.pointee.pw_dir, isDirectory: true, relativeTo: nil)
}

Next, let’s talk about App Review. To start, be aware that I don’t work for App Review, and thus I can’t make definitive statements on their behalf. However, my understanding is that App Review carefully audits any use of file access temporary exceptions. I’m skeptical that you’ll be able to convince them that your exception is justified.

You wrote:

It's the same folder on every system that my app addresses, so why should I have to ask for it?

Because the act of selecting that directory is considered to be explicit authorisation by the user for your app to access that directory. One of the key legs of Apple’s privacy stance is transparency, and implicit access like the one you’re proposing is not in any way transparent.

As it's in

~/Library
, it's difficult to get users to reliably find that folder, considering it's usually hidden.

True. I think you can help with this by setting the default directory of the open panel appropriately, based on the real home directory you calculated above.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thanks Quinn - much appreciated. Yes - the `getpwuid` got me the real home directory as you suggested, and from there the sandbox temporary exception appears to work as expected.


I understand the comments about App Review - I guess I'll have to see what they say about it soon. Given the entire premise of my app is to modify the contents of this other app's Application Support folder, I'd suggest that's transparent enough 😉 - but we'll see. At least, now that I can get to the real home directory, I can set this as the default directory for an open panel, as you suggest. Not ideal, not elegant - but should pass review at least.

Hi Quinn. First of all, I need to say that you are an Apple person that really helps. Thank you.

My case its similar. I am trying try to test a game before submitting it to MAS. Especially, I am trying to test that IAP works correctly. This is a game made in Unity.

I am code-signing correctly, and sandboxing the game to test authentication and then IAP. But I get several errors. I tried to identify one of them and this is the result...

Apparently, when trying to access the User folder the application returns some inexistent folder within User/Library/Containers. More exactly this folder: /Users/user/Library/Containers/com.company.game

And since the folder is not there, several things fail to work. I guess this happens when the Sandbox security entitlement is set. But the folder is not there, so what is necessary in order to make the OSX to create this folder?

Apparently, when trying to access the User folder

What do you mean by “the User folder” here? If a non-sandboxed app I’d interpret that to mean the user’s home directory. In a sandboxed app that gets directed to the root of your app’s container. That container is created for you by the system when it launches your app.

I just created a new sandboxed app called Test107593. In it I have this code:

let userDir = FileManager.default.homeDirectoryForCurrentUser
print(userDir.path)

That prints:

/Users/quinn/Library/Containers/com.example.apple-samplecode.Test107593/Data

And that directory was created on launch:

% ls -ld "/Users/quinn/Library/Containers/com.example.apple-samplecode.Test107593/Data"
drwx------  12 quinn  staff  384  2 Aug 09:57 /Users/quinn/Library/Containers/com.example.apple-samplecode.Test107593/Data

Share and Enjoy

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

Thank you Quinn, in my app the container folder is not being created.

Do I need to add a particular Sandbox security entitlement?

By user I mean my username.I should have used a different word sorry...

 /Users/andres/Library/Containers/com.company.game

in my app the container folder is not being created.

Weird.

Do I need to add a particular Sandbox security entitlement?

To create the container? No. Your app is either sandboxed or it isn’t, based on the com.apple.security.app-sandbox entitlement. If it is, the system should set up the container for you.

Containers are persistent state and sometimes — mostly during development, where you’re creating and re-creating the app with different configurations — I’ve seen the system get confused about them, where your container exists but you don’t have the right access to it.

Try this:

  1. Make sure your app isn’t running.

  2. In Finder, navigate to ~/Library/Containers.

  3. Select any containers that are likely to be associated with your app and drag them to the Trash.

  4. Continue with your development.

Share and Enjoy

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