It seems that the application should hold onto the security scoped permission as long as the document is in the (manually managed) recent list.
In my case the error manifested in that
- the Clear Menu menu item in File / Open Recent stayed grayed out
- the recent list did not persist across app launches
- the recent list did not appear on the Dock.
- I got errors in Console.app saying
Insert failed for list identifier com.apple.LSSharedFileList.ApplicationRecentDocuments Error: Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted" (Access to list denied) UserInfo={NSDebugDescription=Access to list denied}
It seems that internally the recent documents list is managed by a separate thread, which in turn communicates with the system process sharedfilelistd
. If I stopAccessingSecurityScopedResource
, I can observe a sandboxd
violation in Console.app with the following call stack (abbreviated):
Sandbox: MyApp(24093) deny(1) file-read-data /path/to/recent/document
Thread 1 (id: 364320):
0 libsystem_kernel.dylib 0x00007ff81b2265b2 mach_msg2_trap + 10
1 libsystem_kernel.dylib 0x00007ff81b22d5e4 mach_msg_overwrite + 692
2 libsystem_kernel.dylib 0x00007ff81b22689a mach_msg + 19
3 libdispatch.dylib 0x00007ff81b0dce6f _dispatch_mach_send_and_wait_for_reply + 518
4 libdispatch.dylib 0x00007ff81b0dd273 dispatch_mach_send_with_result_and_wait_for_reply + 50
5 libxpc.dylib 0x00007ff81afacb97 xpc_connection_send_message_with_reply_sync + 238
6 Foundation 0x00007ff81c18b423 __NSXPCCONNECTION_IS_WAITING_FOR_A_SYNCHRONOUS_REPLY__ + 9
7 Foundation 0x00007ff81c1893c1 -[NSXPCConnection _sendInvocation:orArguments:count:methodSignature:selector:withProxy:] + 3215
8 Foundation 0x00007ff81c188723 -[NSXPCConnection _sendInvocation:withProxy:] + 91
9 CoreFoundation 0x00007ff81b3234c6 ___forwarding___ + 671
10 CoreFoundation 0x00007ff81b323198 _CF_forwarding_prep_0 + 120
11 SharedFileList 0x00007ff823244145 __44-[SFLGenericList _insertItem:atIndex:error:]_block_invoke + 695
12 libsystem_trace.dylib 0x00007ff81afeaa0e _os_activity_initiate_impl + 51
13 SharedFileList 0x00007ff823243ddc -[SFLGenericList _insertItem:atIndex:error:] + 301
14 SharedFileList 0x00007ff823243ba5 __46-[SFLGenericList _insertItem:afterItem:error:]_block_invoke + 378
15 libsystem_trace.dylib 0x00007ff81afeaa0e _os_activity_initiate_impl + 51
16 SharedFileList 0x00007ff823243961 -[SFLGenericList _insertItem:afterItem:error:] + 316
17 SharedFileList 0x00007ff8232433b2 -[SFLGenericList insertItem:afterItem:error:] + 100
18 SharedFileList 0x00007ff82323c085 +[SFLList(LSSharedFileListSupport) itemByInsertingAfterItem:name:URL:propertiesToSet:propertiesToClear:list:] + 1370
19 SharedFileList 0x00007ff82323f3f7 LSSharedFileListInsertItemURL + 183
20 AppKit 0x00007ff81e80868f -[_NSRecentItemsMenuController _notePendingRecentDocumentURLsForKey:documentsSnapshot:] + 677
21 Foundation 0x00007ff81c1ada71 __NSBLOCKOPERATION_IS_CALLING_OUT_TO_A_BLOCK__ + 7
22 Foundation 0x00007ff81c1ad969 -[NSBlockOperation main] + 98
23 Foundation 0x00007ff81c1ad902 __NSOPERATION_IS_INVOKING_MAIN__ + 17
24 Foundation 0x00007ff81c1acc02 -[NSOperation start] + 782
25 Foundation 0x00007ff81c1ac8e8 __NSOPERATIONQUEUE_IS_STARTING_AN_OPERATION__ + 17
26 Foundation 0x00007ff81c1ac7b6 __NSOQSchedule_f + 182
27 libdispatch.dylib 0x00007ff81b0d1771 _dispatch_block_async_invoke2 + 83
...
What this shows is there is a thread started by the system, performing some asynchronously dispatched operation called [_NSRecentItemsMenuController _notePendingRecentDocumentURLs...]
, which in turn issues XPC calls to a SFL (shared file list), which somehow results in an attempt to read the specified document, which is blocked by App Sandbox.
We can therefore conclude that after calling noteNewRecentDocumentURL
, the application still has to hold the permission to access the file because the actual management of the recent list happens on another thread asynchronously, and that thread has to be able to check that the file exists or read it for some other reason.
I rewrote my application so that it holds onto the security scoped resources as long as the associated files are in the recent list, and stops accessing them when they are removed from the list. Internet wisdom says there are a few thousand such resources available, see What are the current kernel resource limits on security-scoped bookmarks?. Since my recent list never grows to such a size, my application will never run out of kernel resources.
See also https://developer.apple.com/forums/thread/710278