Security Scoped File URL Bookmarks

Sandboxed applications (without any additional entitlements) live within their container (~/Library/Containers/apps.bundleidentifier_) and have no access to the rest of the File System, no access to the Internet, no access to Hardware such as the Camera, Microphone, USB Devices and Printing and has no access to User’s Data such as the Address Book, Location or Calendar.

Now this may seem a little extreme, but besides file system access all of these features can be enabled by requesting the relevant entitlement. For these features everything “just works”, but be warned you may be quizzed by the Apps Review Team why you require a given entitlement, so don’t just include them for the sake of it … or for analytics.

So now on to files…

When you sandbox your application you will have the following entitlements file (typically called appname.entitlements_):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>com.apple.security.app-sandbox</key>
    <true/>
</dict>
</plist>

com.apple.security.app-sandbox simply enables app sandboxing for the given target:

com.apple.security.app-sandbox
Enables App Sandbox for a target in an Xcode project

As you can’t just access any file on the file system, the user has to select them (which means the powerbox will give you temporary access to the URL of the file). To do this the user can use drag and drop, or use NSOpenPanel/NSSavePanel. For this example we will use NSOpenPanel for clarity. To use NSOpenPanel/NSSavePanel you need to include the following entitlement:

com.apple.security.files.user-selected.read-write
Read/write access to files the user has selected using an Open or Save dialog

NSOpenPanel is trivial to implement and you get access to a file URL in the completion handler:

NSOpenPanel *openPanel = [NSOpenPanel openPanel];

[openPanel beginSheetModalForWindow:[self window] 
completionHandler:^(NSInteger result){

if (result == NSOKButton) 
{
    NSURL *openPanelFileURL = [openPanel URL];
}

}];

By using NSOpenPanel you now have access to the given file URL (and therefore file) until your application quits. Under certain circumstances you also get access to the file URL when you application launches if the application supports resume.

So what if you need to access files across launches?

You need another entitlement of course, in this case you have 2 choices depending on if your Application is a Document Based Application:

com.apple.security.files.bookmarks.collection-scope
Ability to use document-scoped bookmarks and URLs

or a Non-Document Based Application:

com.apple.security.files.bookmarks.app-scope
Ability to use app-scoped bookmarks and URLs

Assuming your application is a Non-Document Based Application your entitlements file will now look like this:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>com.apple.security.app-sandbox</key>
    <true/>
    <key>com.apple.security.files.user-selected.read-write</key>
    <true/>
    <key>com.apple.security.files.bookmarks.app-scope</key>
    <true/>
</dict>
</plist>

First of all you need to get access to the file URL in the same way as you did before, but this time you are going to create a bookmark out of it using NSURLBookmarkCreationWithSecurityScope:

NSURLBookmarkResolutionWithSecurityScope
Specifies that the security scope, applied to the bookmark when it was created, should be used during resolution of the bookmark data.
NSError *error = nil;
NSData *bookmarkData = nil;

bookmarkData = [openPanelFileURL 
bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope
includingResourceValuesForKeys:nil
relativeToURL:nil
error:&error];

You can now store this bookmark (which is just a NSData object) in any way you choose, as long as you can retrieve it later.

When you want to access this file you need to convert the bookmarkData into a file URL:

NSError *error = nil;
BOOL bookmarkDataIsStale;
NSURL *bookmarkFileURL = nil;

bookmarkFileURL = [NSURL 
URLByResolvingBookmarkData:bookmarkData
options:NSURLBookmarkResolutionWithSecurityScope
relativeToURL:nil
bookmarkDataIsStale:&bookmarkDataIsStale
error:&error];

The URL returned includes a “security scope” appended to it (although this cannot be assumed):

file://localhost/Users/ObjColumnist/Desktop/File?applesecurityscope=373861333331353430643963323736623939346438646161643134663339363061396361306534303b30303030303030303b303030303030303030303030303032303b636f6d2e6170706c652e6170702d73616e64626f782e726561642d77726974653b30303030303030313b30653030303030323b303030303030303030306535363566373b2f75736572732f7370656e6365722f6465736b746f702f77617463686564

You then need to tell the OS that you are going access this file URL:

[bookmarkFileURL startAccessingSecurityScopedResource];

This allows you to do anything you want with that file URL, typically you will use NSFileManager to do this.

Once you are done accessing this file URL you MUST tell the OS, failing to do so will leak kernel resources and prevent you from accessing secured files until you quit your application.

[bookmarkFileURL stopAccessingSecurityScopedResource];

This wraps up Security Scoped File URL Bookmarks on OS X and how you can access files outside of you application’s sandbox container. It is important to note that you don’t have to do this for files that reside in your application’s container, and this article doesn’t do any error handling when converting URLs to Bookmarks and Vice Versa.