Swift file reading permission error on macOS sandbox

I'm trying to read the contents of a file on the filesystem in a macOS Swift app (Xcode 9 / Swift 4).

I'm using the following snippet for it:


let path = "/my/path/string.txt"
let s = try! String(contentsOfFile: path)
print(s)


My problem is the following:


1. This works in a Playground

2. This works when I use the Command Line Tool macOS app template

3. This terminates in a permission error when I use the Cocoa App macOS app template


The permission error is the following:

Fatal error: 'try!' expression unexpectedly raised an error:
Error Domain=NSCocoaErrorDomain Code=257 "The file "data.txt" couldn't be opened because you don't have permission to view it."
UserInfo={NSFilePath=/my/path/data.txt, NSUnderlyingError=0x60c0000449b0 {Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted"}}


I guess it's related to sandboxing but I found no information about it.


1. How can I read from the filesystem in a sandboxed app? I mean there are so many GUI apps which need an Open File dialog, it cannot be a realistic restriction of sandboxed apps to not read files from outside the sandbox.

2. Alternatively, how can I switch off sandboxing in Build Settings?

3. Finally, I tried to compare the project.pbxproj files between the default Cocoa Apps and Command Line Tool template and I didn't see any meaningful difference, like something about security or sandbox. If not here, where are those settings stored?

Answered by QuinceyMorris in 291450022

You are correct that this is a sandboxing restriction.


>> there are so many GUI apps which need an Open File dialog, it cannot be a realistic restriction of sandboxed apps to not read files from outside the sandbox


Sandboxed apps cannot read files from outside the sandbox, except where explicitly given permission by the user via an Open File dialog, which returns a "security scoped URL".


You can turn off sandboxing by selecting the project item in the navigator pane, and choosing the Entitlements tab of the project editor. This is OK to do, but it means you can't distribute the app through the Mac App Store. (It should still be code-signed, though, for ad-hoc distribution, to avoid being blocked by GateKeeper.)


Or, you can let your app be sandboxed, and use NSOpenPanel to get permission from the user. In this case, you can save a security scoped bookmark from the URL that can be re-consistituted the next time the app launches, so you only need to get permission once.


Or, if the file you're trying to read, you can arrange to place it inside the sandbox somewhere (such as the Application Support folder, if not you app bundle), so permissions are not an issue.


Finally, all modern Mac apps should not use path-based APIs except in extremely unusual circumstances. URL-based APIs are always preserved. (One reason: there are no security-scoped paths, only URLs.)

Accepted Answer

You are correct that this is a sandboxing restriction.


>> there are so many GUI apps which need an Open File dialog, it cannot be a realistic restriction of sandboxed apps to not read files from outside the sandbox


Sandboxed apps cannot read files from outside the sandbox, except where explicitly given permission by the user via an Open File dialog, which returns a "security scoped URL".


You can turn off sandboxing by selecting the project item in the navigator pane, and choosing the Entitlements tab of the project editor. This is OK to do, but it means you can't distribute the app through the Mac App Store. (It should still be code-signed, though, for ad-hoc distribution, to avoid being blocked by GateKeeper.)


Or, you can let your app be sandboxed, and use NSOpenPanel to get permission from the user. In this case, you can save a security scoped bookmark from the URL that can be re-consistituted the next time the app launches, so you only need to get permission once.


Or, if the file you're trying to read, you can arrange to place it inside the sandbox somewhere (such as the Application Support folder, if not you app bundle), so permissions are not an issue.


Finally, all modern Mac apps should not use path-based APIs except in extremely unusual circumstances. URL-based APIs are always preserved. (One reason: there are no security-scoped paths, only URLs.)

Thanks a lot, the keyword was Entitlements! I was able to Google the Apple docs using this as well as I finally see where are those settings stored in the project.

I know this is old, but it helped me a lot, so thank you. Interesting to note that in Xcode 12.3 the menus and wording to get to the Sandbox setting are different and to me they don't seem to match what's shown in the Xcode help, either. But from what I read here and a little educated guessing / poking around I got there.

I'm still searching for the location of the settings, bc. there is no "Entitlements" tab. Do you have a screenshot, where to find?

On modern versions of Xcode you configure the App Sandbox via the App Sandbox capability in the Signing & Capabilities editor.

Share and Enjoy

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

Does this apply to Xcode iOS projects too?

Does this apply to Xcode iOS projects too?

Yes and no. On macOS, sandboxing is optional [1]. On iOS, all apps are sandboxed all the time. Thus, there’s no App Sandbox capability in the Signing & Capabilities editor.

The iOS sandbox are similar to the App Sandbox on macOS but it’s not exactly the same. In some cases it’s stricter and in some cases it’s more relaxed. For example, a sandboxed macOS app must declare its use of the network whereas an iOS app can always use the network.

Share and Enjoy

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

[1] Except for Mac App Store apps, where App Review requires it.

I had this problem with trying to create an NSImage from a URL retrieved from an openDialog (well, a SwiftUI .fileImporter) even after setting the UserSelectedFile Sandbox permission. (If it's 'none', it'll crash, so it's hard to get that one wrong). The fileImporter retrieves a valid URL, but cannot access the file anyway. (This used to work).

The answer is to make the resource explicitly accessible:

_ = selectedFileURL.startAccessingSecurityScopedResource()
    guard let image = NSImage(contentsOf: selectedFileURL)  else { return }                    selectedFileURL.stopAccessingSecurityScopedResource()
Swift file reading permission error on macOS sandbox
 
 
Q