How to securely communicate between sandboxed Mac apps in the same App Group?

Hello,
this question was asked in various forms a lot on this forum but I'm probably still missing the point, so I would like to ask if anyone can help me to clarify that :)

I have two apps which need to communicate, both are sandboxed, hardened and in the same app group. In my particular setup it's Main.app and Helper.app, where Helper.app is embedded inside the Main.app (but I don't think that's that particularly important, just saying that in case it could be helpful).

I was able to use CFMessagePort to make the communication possible by using port name prefixed with the app group id (like described here) that does exactly what I need expect it's not secure, namely I can't verify sender and all communication is basically public (any client knowing the name of the port can send messages there). I understand that CFMessagePort is kinda ancient leftover waiting to be deprecated and from reading its open sourced code from 2015 I understand that this requirements can't be met because (in)security is built in the code :(

I turned my hopes towards XPC and was able to start the NSXPCListener with mach service in the Main.app and connect to the mach service with NSXPCConnection from the Helper.app. Unfortunately that works only from Xcode and I guess it has something to do with the debugserver being parent of the both apps not the launchd. Also for what it's worth, I was not able to verify the sender with SecCodeCopyGuestWithAttributes anyway as I got kPOSIXErrorEPERM (I tried the workaround with entitlement to access /private/var/db/mds).

As I understood from other posts in this forum, this usage pattern is not supported and I have to have meditator in between the apps (the broker), which will allow both apps to exchange NSXPCListenerEndpoint and establish direct communication. Unfortunately again, the XPC service launched as private to the app can't be used for this because the other app can't connect to it, so I have to create .plist for launchd and load the service manually. .

...That's where I got stuck because I don't know how I would get this into App Store. As far as I know the LaunchAgents and Daemons are prohibited and only allowed are LoginItems, which don't seem suited for this to me. Also compared to CFMessagePort this solution with broker is total overkill to problem in the hand, technically I just need to send few "events" between the apps in acceptable time.

So to sum up my "question": Is this XPC wrangling my only option? I was considering using raw mach messaging, which documentation is discouraging from on every step it seems or unix domain socket, which is kinda possessing the same security problems as CFMessagePort.

Sorry for long post and thank you for reading it :)
Why isn't Helper.app implemented as a normal XPC? And for that matter, why is it a separate helper?
That's good question - normal XPC service is not a fit for the task. We have an application (the main application) which represents some kind of account and recently we got ability to log in into another account, but that's confusing for users so we need to separate the second account to be something which is sitting in the Dock and user can click it, pin it, etc. For that purpose I created dummy app (the helper) which is just sitting in the dock doing nothing[1] and when activated, it sends message to main application which takes over and presents proper UI to the user[2].

Notes:
[1] - We do lot more than that - main app configures badge and menu items of the helper app as well as helper app is sending more events that just the activation, e.g. hide, terminated, menu item action, etc.
[2] - I'll not deny that this is bonkers but our other option is to cut into slices the existing application, which is built as monolith and thank's to it's multi-platform nature that would take forever...so I would rather wait for next rewrite :)

thank's to it's multi-platform nature that would take forever

You will need to expand on that aspect. Many of these cross platform packages, and the people who use them, do absolutely crazy things that no one has ever seen before.

Otherwise, I'm not sure what kind of built-in security you are looking for. App groups don't provide any security, they provide a hole punched through the built-in sandbox security. That being said, the punched hole is restricted to apps in the app group. Because the app is still sandboxed, I think the sandbox will not allow public access to your message port. See the documentation here.

You will need to expand on that aspect.

The UI of the Main app is Electron, the infamous Frankenstein's monster of node.js and Chromium, but the code which is handling all macOS integration bits and pieces is our native node.js add-on written int Objective-C/C++.

Because the app is still sandboxed, I think the sandbox will not allow public access to your message port.

I got the same impression, but after trying that [1] I found out that the hole is open for everyone who knows the port name, which is trivial to get. The source code from 2015 of CFMessagePort.c [2] verifies my assumption - it is registering the port as bootstrap service under the port name with bootstrap_register(), so basically only sandboxed apps outside of app group are restricted from accessing it :)

I'm not sure what kind of built-in security you are looking for.

At minimum level we need to be able to verify the sender identity (i.e. if the sender is app signed by us). Ideal situation would be to have fully private communication channel secured by app signature on the OS level.

I believe I can achieve the first requirement by using raw mach messaging (with MACH_RCV_TRAILER_AUDIT) but this stuff is not exactly well documented (or I'm looking to wrong places) and I'm afraid that I will sooner or later hit something which Apple considers private API, effectively preventing the app from being accepted to App Store :(


[1] - I wrote command line app (unsigned - without entitlements) and was able to open the port with CFMessagePortCreateRemote() and communicate without any problem. (on Big Sur 11.1 (20C69) with Xcode 12.3 (12C33))
[2] - https://opensource.apple.com/source/CF/CF-1151.16/CFMessagePort.c.auto.html

The UI of the Main app is Electron

That complicates things. I strongly suggest building a non-Electron proof-of-concept for any solution just so you know how it should work and where any complications might come from.

only sandboxed apps outside of app group are restricted from accessing it :) 

Important plot point. Sandbox security is different depending on your perspective. It provides security to the user against your app, not against other apps.

At minimum level we need to be able to verify the sender identity (i.e. if the sender is app signed by us). Ideal situation would be to have fully private communication channel secured by app signature on the OS level. 

I don't know of any built-in API that is going to give you that guarantee. This would be easy enough to solve by just getting rid of the multiple executables. So which is more important? The security or the electron?

I strongly suggest building a non-Electron proof-of-concept (...)

That's what I did, everything I'm describing here was done on pure Mac app (no Electron in the sight), but the final thing is of course integrated with Electron. I mentioned the Electron just to illustrate why we can't just re-build our app.

 I don't know of any built-in API that is going to give you that guarantee.

As far as I know the first part is achievable with raw mach ports (you can instruct kernel to add audit token to message trailer), there is just bunch of annoying bookkeeping with service bootstrap.

I think I got my answer - there is simply no better solution to my problem, from security or practicality point of view, than raw mach ports :(

Thank you Etresoft, really appreciate you spent the time on this.
Please please please try to stay away from raw Mach ports. They are extremely hard to use correctly and are likely to cause much grief in the long term.

As to what you should do, that’s tricky. macOS does not have very good support for IPC between peer apps. Its architecture is centred around the concept of clients connecting to servers (for example, apps to agents and daemons, or agents to daemons).

Unfortunately I just don’t have time to work through your specific case in the context of DevForums. My advice is that you open a DTS tech support incident so I can allocate time for an in-depth look. Please make sure to reference this thread so that your incident gets routed to me.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@apple.com"
How to securely communicate between sandboxed Mac apps in the same App Group?
 
 
Q