I needed a secure XPC Mach Services connection and didn't want to be directly using the XPC C API throughout my codebase, so I created the SecureXPC framework for Swift. It uses the aforementioned SecCodeCreateWithXPCMessage API on macOS 11 and later, and falls back to as eskimo says the "non-public stuff" on older versions. Sharing this in the hopes it's helpful to people here. Feedback most welcome over on the Github page either as issues or discussions!
Post
Replies
Boosts
Views
Activity
After spending a while trying to figure the best approach for this problem, I eventually settled on using the XPC C API which allows for securing the connection using the SecCodeCreateWithXPCMessage function on macOS 11 and later, and falling back to using a private function on older versions. But I didn't want to have to directly use the C API all throughout my codebase so I created the SecureXPC framework in Swift. Hopefully this is helpful to some people here. Feedback is most welcome on the Github page either as issues or discussions!
Wow, thanks for the speedy reply! Now I'm wondering where in the world you're located 🤔
Is this because it’s a good idea in general? Or because you’re targeting the Mac App Store?
Kind of both. For my own case I'm not targeting the App Store. However, I've been writing an open source Swift package that encapsulates all of the general XPC security, routing, error handling, etc. So ideally I'd come up with a solution that's acceptable for Mac App Store apps as well as my own needs.
However, when you look at it from the per-process privilege model now supported on the Mac, this may well represent a privilege escalation.
That's an excellent point.
I'm using the C API (I found the Objective-C one to be quite an awkward fit with Swift) so xpc_connection_set_peer_code_signing_requirement looks promising. Unfortunately since it was just released in macOS 12, I don't think I'll be able to make use of that for at least a couple years.
The security-scoped URL sounds promising, I'll make a go at that approach. Much appreciated, it never would've occurred to me do to that. Am I correct in saying this would be an app-scoped bookmark, the app won't need any additional entitlements, and the login item would need the com.apple.security.files.bookmarks.app-scope entitlement in order to call startAccessingSecurityScopedResource and stopAccessingSecurityScopedResource?
Hmm, in trying to implement this I'm getting the sense this may not be possible as it seems neither an app-scope nor a document-scoped bookmark matches what you're describing. Possibly there are undocumented/under documented ways of making one or both of these work?
From bookmarkData(options:includingResourceValuesForKeys:relativeTo:):
For an app-scoped bookmark, no sandboxed app other than the one that created the bookmark can obtain access to the file-system resource that the URL (obtained from the bookmark) points to. Specifically, a bookmark created with security scope fails to resolve if the caller does not have the same code signing identity as the caller that created the bookmark.
For a document-scoped bookmark, any sandboxed app that has access to the bookmark data itself, and has access to the document that owns the bookmark, can obtain access to the resource.
and then elsewhere on that same page:
If you are creating a security-scoped bookmark to support App Sandbox, use this parameter as follows:
To create an app-scoped bookmark, use a value of nil.
To create a document-scoped bookmark, use the absolute path (despite this parameter’s name) to the document file that is to own the new security-scoped bookmark.
My interpretation of this is that I must create a document-scoped bookmark so that the login item process is capable of obtaining file-system access. However, if I use a document-scoped bookmark what absolute path would I be providing since there is no document file?
Huh, okay so what actually worked was creating a bookmark that doesn't have either of these scopes. Is this supposed to work?
In the app:
let bookmark = try Bundle.main.bundleURL.bookmarkData()
Send that over the XPC connection, and then in the login item:
var isStale = Bool()
_ = try URL(resolvingBookmarkData: bookmark, bookmarkDataIsStale: &isStale)
Neither the app nor the login item have any additional entitlements and as you can see I'm doing nothing at all with the URL instance that's created.
This works perfectly, but I don't understand why and I'm concerned it could break at any time in the future.
I suspect what’s happening here is that you’re benefitting from the bookmark’s implicit security scope.
Thanks for the explanation! I didn't realize such behavior was implicit.