Security-scoped bookmarks allow you to store access to a given user-selected URL. They are commonly used on macOS to store access information for a user-selected directory. Restoring the security-scoped bookmark data allows you to regain access to a folder previously selected by the user.
I’ve been using this technique for years and recently discovered a major bug in macOS Sequoia that’s been fixed in macOS 15.1. While it shows the vulnerability of this API, I still want to highlight the added value when working in a sandboxed environment.
What is a security-scoped bookmark?
A security-scoped bookmark is a data object that contains URL bookmark data with a security scope applied. You typically use this after gaining access to a given URL. For example, on macOS you might be using an NSOpenPanel
to let the user select a given destination.
The resulting URL contains bookmark data, which you can retrieve with a security scope applied:
let url = aUserSelectedURL
let bookmarkData = try url.bookmarkData(
options: .withSecurityScope,
includingResourceValuesForKeys: nil,
relativeTo: nil
)
You can store the resulting bookmark data inside a storage of your choice, such as User Defaults or the Keychain. Upon resolution, it provides a security-scoped URL that allows you to read/write access to the file-system resource similarly to the active session in which you gained access to the URL:
var bookmarkDataIsStale: Bool = false
let bookmarkData = try URL(
resolvingBookmarkData: storedBookmarkData,
options: .withSecurityScope,
relativeTo: nil,
bookmarkDataIsStale: &bookmarkDataIsStale
)
It’s essential to keep track of the state of the bookmark data. In case bookmarkDataIsStale
returns true, you must refresh the bookmark data you’ve stored in the User Defaults or container of your choice. Otherwise, you might lose access and need to ask the user for permission again.
macOS Sequoia “Failed to retrieve app-scope key” bug
Many of the apps that make use of security-scoped bookmarks require functionality in their foundation. If the security-scoped mechanism stops working, it might break an app’s functionality since the user-selected destinations are no longer accessible.
I’m using bookmarks for my developer tool RocketSim to store access to a user-selected Xcode application directory. Without access, RocketSim will stop working altogether. Since the release of macOS Sequoia, several users have reached out with Xcode selection issues. It turned out that it was caused by a bug in the “ScopedBookmarksAgent”:
What you’re hitting is bug in “ScopedBookmarksAgent” which can cause it hang if it happens to have been launched when the keychain was also locked (for example, late in the screen lock process). That bug is fixed as of macOS 15.1 beta 4.
If you’re hitting it regularly during development, you can resolve the issue by killing ScopedBookmarksAgent (you may also need to kill and relaunch your app, depending on what state it ends up). On the user side, a log out (or reboot) should resolve the issue.
The issue is causing many apps to run into unexpected behavior without a simple solution for the users to resolve. The provided solution is to ask users to kill ScopedBookmarksAgent
, which is not a realistic thing to ask. Therefore, I decided to store the bookmark data in memory, resulting in my users only having to provide access once per session. It’s not ideal, but it’s better than an app that stops working completely.
Secondly, I’ve informed my users about the issue inside the permissions screen:
This does not solve the issue, but it at least informs users that the problem they’re encountering is caused by an uncontrollable source.
Conclusion
Security-scoped bookmarks allow you to store permissions for a given URL resource. You can store the bookmark data in a container of your choice and regain access in a future app session by restoring the security-scoped URL. Apart from a bug on macOS Sequoia, it’s the industry standard for storing permissions to a user-selected destination.
If you like to improve your Swift knowledge, even more, check out the Swift category page. Feel free to contact me or tweet to me on Twitter if you have any additional tips or feedback.
Thanks!