In the WWDC 2018 session "Your Apps and the Future of macOS Security", Apple announced big changes to macOS security.
One of them - and possibly the one with the biggest impact: apps can no longer send Apple Events to other apps without user authorization.
Apple argues that Apple Events (which AppleScript uses under the hood) can be used to get access to otherwise protected user data in other apps, so the user should be prompted for authorization.
I concur. However, the current implementation turns out to be problematic for many legitimate, privacy-respecting apps in (at least) the automation, accessibility, device management, utility and remote control categories.
The current implementation
The first time Sender.app sends an Apple Event to Recipient.app:
- the Sender.app thread sending the Apple Event is blocked
- Recipient.app is launched, if it's not already running
- the user is prompted for authorization
The user can then choose between these two options:
- Don't Allow: the thread resumes execution. The error code
errAEEventNotPermitted
(-1743) is returned to Sender.app for this and other Apple Events sent to Recipient.app in the future. - OK: the thread resumes execution. This - and future Apple Events from Sender.app to Recipient.app - are delivered without prompting the user again.
Noteworthy:
- Recipient.app is launched every time an Apple Event is sent in its direction - even if the user has chosen "Don't allow" before and the Apple Event isn't delivered.
- the user is only ever prompted once for every sender / recipient pair.
Problem 1: authorization status is not available to apps
As of macOS Mojave beta 2, apps can't get their current Apple Event authorization status. In consequence:
-
Apps can't time authorization
Without information on its current authorization status, an app can't time when the user is asked for authorization.
If the app knew that authorization has not yet been requested, it could, f.ex. bring up onboarding UI right after launch.
Without that information, the app can't guard against the authorization prompt popping up at inappropriate or non-obvious times.
-
Apps can't adapt their UI consistently
Apps that offer additional features if authorized can't consistenly adapt their user interface without knowing their authorization status.
Even if an app saved the previous result of sending an Apple Event to another app (and thereby the user's choice): how could it know about changes the user makes in System Preferences after that?
-
Apps can't alert users to the lack of authorization
Apps relying on the ability to control another app might want to provide inline information about the current authorization status. For example in their settings - or as part of built-in diagnostics.
Right now, that information can't be retrieved without launching the target app and the risk of triggering an authorization prompt out of context.
-
Apps can't avoid getting stuck indefinitely if there's no one to answer the authorization prompt
Without knowing their current authorization status, apps can't guard against triggering an authorization prompt that blocks their sending thread indefinitely (this is bad).
This can be particularly problematic for device management (if a script never finishes) and apps that provide users with accessibility or remote access to their Mac (if blocking the calling thread means to effectively lock out the user).
We've had an API to check for an app's accessibility status since macOS 10.9 Mavericks.
macOS Mojave adds an API for apps to get their current Camera and Microphone authorization status for pretty much the same reasons: timing, adapting UI, good user experience.
Possible solution: add a similar API for Apple Events.
Problem 2: apps can't request approval without launching the target app
As of beta 2, apps can only prompt for authorization indirectly - by way of sending an Apple Event to the target app. This has several implications:
-
The target app gets launched
Sending an Apple Event makes macOS automatically launch the target app. This makes it impossible to prompt the user for approval for apps without launching them.
Imagine what this makes the onboarding process look like for remote control apps like mine that target 100+ apps:
- every app the user wants to control would need to be launched at least once
- the authorization prompt needs to be read and answered for every app
- the target app needs to be quit
-
There's no way to re-request authorization
Think of a feature that involves controlling another app through Apple Events, for which the user has previously declined authorization. Possibly in error, or because it was requested in a different context - and the user didn't see the point.
Whatever the reason, the user now wants to use the feature and clicks its button.
The app gets to work, but sending the Apple Event to the target app fails with
errAEEventNotPermitted
. The app brings up an alert informing the user that the feature is not available because the app hasn't been authorized.How cool would it be for the user if he could now just click the "Authorize" button in the alert - and quickly go through the authorization prompt again? Compared to easy to miss instructions on which checkbox needs to be checked in System Preferences - and how to get to it.
Possible solution: add an API to request authorization that is repeatable and doesn't require launching the app (similar to what was added to AVFoundation to request authorization for accessing camera and microphone - or what has been available for accessibility since macOS 10.9 Mavericks).
Problem 3: No "allow all" option to whitelist apps
What about apps that control a large number of other applications - or whose list of target apps is open-ended?
As of Beta 2, there's no adequate support for them. In order to get them fully working on macOS Mojave, users would need to get prompted for and authorize every targeted app individually.
But even if an app could/would do that as part of an on-boarding step, users would have to be prompted again for every targeted app that's installed thereafter.
Apple currently uses a private entitlement (kTCCServiceAppleEvents
) to exempt Script Editor and Automator from Apple Event sandboxing, so users of these Apple apps will never see an authorization prompt.
Making this entitlement available to developers would of course defeat the whole purpose of Apple Event sandboxing. So that's not an option.
Users, however, should be able to pre-approve apps they trust, so these can freely send Apple Events to any other app - without bringing up prompts.
For access to protected user files, macOS Mojave already provides this capability. Seemingly at least in part to avoid bringing up multiple approval prompts. From session 702:
Apps that traverse the user's home folder could trigger multiple approval prompts, not just for Photos, Contacts, Calendars and so on. [..] So users can pre-approve such apps by adding them to the new "Application Data" category in the System Preferences Security & Privacy pane.
Possible solution: provide a similiar option in the Security & Privacy pane, for pre-approving apps to send Apple Events without limitations.
Problem 4: No support for usage descriptions
When prompting for access to Photos, Contacts, Calendars, etc., macOS Mojave allows apps to add purpose text to that prompt. From session 702:
When the user is prompted to authorize access to their personal data, it is important to communicate the purpose of that access.
Imagine that you've just installed an app that you've never used before. And the very first time you launch it, you saw this prompt [without purpose text]. That's a tough decision.
We can make that decision easier by including purpose text.
As of macOS Mojave Beta 2, Apple Event approval prompts don't support purpose text.
Apple is spot on that communicating purpose is important - and helps users with tough decisions. So this is something that really should be supported when prompting for Apple Events as well.
Possible solution: add a NSAppleEventsUsageDescription
key to Info.plist
Possible solutions
After talking about the rough edges and missing parts in the current implementation, here are my ideas for addressing them:
API to query the current status
Inspired by this AVFoundation API, this is what the API providing authorization status for sending Apple Events could look like:
Using an NSAppleEventDescriptor
object as argument, this API could provide the authorization status for apps targeted by URL, bundle identifier or process ID (pid).
Passing a nil
value could request information on whether the app has been whitelisted to send Apple Events to any app without prompting.
API to request authorization from the user
Inspired by this AVFoundation API, this is what the API to request prompting the user for authorization (without launching the target app) could look like:
Using an NSAppleEventDescriptor
as argument, this API could bring up authorization prompts for apps identified by URL, bundle identifier or process ID (pid).
If the target app described by the Apple Event Descriptor is not yet running, prompting for approval should not lead to launching this app.
Passing a nil
value for eventDesc
could be a trigger to add the app to the "Automation" list (if it's not already in it) and bring up System Preferences with the Security & Privacy pane open on the "Automation" category. This would be very useful for sending people to the right place to make changes.
The options
parameter could provide a way to pass additional options like prompting the user again, even if the user has previously been prompted for authorization.
Finally, by using a completion handler, the user could be prompted for authorization without blocking the calling thread.
System Preferences option to allow apps to control all other apps
Thinking about how an "allow all" or "whitelist" option could be integrated, I've come up with two ideas:
Left: adding a checkbox "Allow automating all apps" below each app.
If the user selects it, it becomes the only checkbox shown for that app. The app is then pre-authorized to send Apple Events to any app.
If the user unchecks it, the user is back to controlling authorization on a per-target basis.
Right: adding a popup next to the app with two options "Ask" and "Allow all".
"Ask" is selected by default and brings up a prompt for every target app.
"Allow all" can be selected by the user to pre-authorize an app to send Apple Events to any other app. If that option is selected, the individual per-app checkboxes are no longer shown for the app.
Allow apps to opt-out of automatic prompts
Some apps could benefit of an option to opt out of automatic approval prompts from the OS altogether. This could possibly be expressed via a new Info.plist key (values: automatic
, never
):
Apps that have opted out:
- would get back
errAEEventNotPermitted
until the user has authorized the app to send Apple Events to the target app. - would be responsible themselves for requesting authorization.
Add support for Usage Description strings
In order to have meaningful and relevant purpose text for every prompt, it should be possible to provide per-app usage descriptions in addition to a default/fallback usage description:
NSAppleEventsUsageDescription
in Info.plist to specify a default/fallback string:<key>NSAppleEventsUsageDescription</key> <string>Remote Buddy needs to send Apple Events to control this application.</string>NSAppleEventsUsageDescriptionStringsFile
in Info.plist to point to a strings file that maps bundle identifiers to app-specific usage descriptions:<key>NSAppleEventsUsageDescriptionStringsFile</key> <string>AppleEventUsageDescriptions</string>And then, in
AppleEventUsageDescriptions.strings
:"com.apple.iTunes" = "Remote Buddy needs permission to display details on the currently playing song.";
I need your help
I am deeply worried that the implementation of Apple Event sandboxing in Beta 2 could make it into the final release of macOS Mojave unchanged.
As it is, it offers too little to developers who want to provide a good user experience. And not enough for utility apps and pro users who are in need of an option to exempt apps from Apple Event sandboxing.
Right now there's a broad and diverse range of useful and beloved apps that take advantage of the Mac's support for automation. They make things "just work", help make the Mac even more accessible, increase productivity and make lifes easier and richer.
For many, these apps are a reason to keep buying Macs - and a part of the Mac's heritage and DNA.
Apple Events are the core technology making these apps possible. It is therefore essential to get right any changes to how Apple Events work. So that these apps can continue to exist and thrive.
If you're using or making any of these apps, please help raise awareness at Apple on the importance of solving the problems presented here - by duping my radar (OpenRadar, Radar) and sharing this blog post.
Thanks! ❤️
Update 2018-07-03
Unlike stated above, sending an Apple event directly to an app (i.e. using NSAppleEventDescriptor
) will not automatically launch the app. Instead procNotFound
(-600) will be returned if the targeted app is not running.
However, addressing a target app via AppleScript (like f.ex. tell application "VLC" to play
) will launch the target app if it is not running. This is true even if the app executing the AppleScript isn't authorized to send Apple events to the target app. That the target app is launched in that case is a convenience afforded by AppleScript, however - and not a result of AppleScript's use of Apple events.
That said, as of beta 2 of macOS Mojave, the target app still needs to be running for the sending app to get errAEEventNotPermitted
in return, or the authorization prompt to pop up. So, as far as the effects in practice are concerned, the above still stands. It is, however, the sending app that needs to take care of launching the target app. "Raw" Apple events will not take care of this for the sending app.
I apologize for the error.
Update 2018-08-30
I wrote a follow-up: macOS Mojave gets new APIs around AppleEvent sandboxing – but AEpocalypse still looms