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:

    1. every app the user wants to control would need to be launched at least once
    2. the authorization prompt needs to be read and answered for every app
    3. the target app needs to be quit
    Launch, approve, quit - up to 100+ times. I don't see many new users willing to sit through this just to try out the app.

  • 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 NSAppleEventUsageDescription 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:

typedef NS_ENUM(NSUInteger, NSAppleEventAuthorizationStatus) { // User has not yet been prompted for permission. NSAppleEventAuthorizationStatusNotDetermined, // User has explicitely denied permission. NSAppleEventAuthorizationStatusDenied, // User has explicitely granted permission. NSAppleEventAuthorizationStatusAuthorized }; @interface NSAppleEventDescriptor (AuthorizationStatus) + (NSAppleEventAuthorizationStatus)authorizationStatusForTarget:(nullable NSAppleEventDescriptor *)eventDesc; @end

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:

typedef NS_OPTIONS(NSUInteger, NSAppleEventAuthorizationRequestOptions) { // No options NSAppleEventAuthorizationRequestOptionsNone, // Prompt the user - even if the user was already prompted in the past NSAppleEventAuthorizationRequestOptionsForcePrompt }; @interface NSAppleEventDescriptor (AuthorizationRequest) + (void)requestAccessForTarget:(nullable NSAppleEventDescriptor *)eventDesc options:(NSAppleEventAuthorizationRequestOptions)options completionHandler:(void (^)(BOOL granted))handler; @end

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):

<key>NSAppleEventAuthorizationMode</key> <string>never</string>

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:

  • NSAppleEventUsageDescription in Info.plist to specify a default/fallback string:
    <key>NSAppleEventUsageDescription</key> <string>Remote Buddy needs to send Apple Events to control this application.</string>
  • NSAppleEventUsageDescriptionStringsFile in Info.plist to point to a strings file that maps bundle identifiers to app-specific usage descriptions:
    <key>NSAppleEventUsageDescriptionStringsFile</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


Bug #41570203 (OpenRadar, Radar)
Title: The new Apple Event sandbox in macOS 10.14 Mojave lacks essential APIs Product: macOS + SDK / Foundation Classification: Bug | Bug Type: Serious Bug | Reproducability: Always Description: The Apple Event Sandbox implementation as of macOS 10.14 Mojave beta 2 (18A314h) lacks essential APIs that apps would need to adapt to and provide a good user experience around the change. -- I identified these issues with the current implementation: 1) timing: apps don't have sufficient control over when the user is prompted for authorization 2) adapting UI: apps have no guaranteed non-interactive/-blocking way to know if they're authorized to send AEs to another app to adapt their UIs 3) apps can't avoid getting stuck indefinitely if there's no one to answer the auth prompt (problem for remote/MDM/accessibility apps) 4) authorization requires launching the target app. Makes on-boarding for tools targeting many apps a UX nightmare. 5) even if trusting an app targeting a large (or even open-ended) list of other apps, users can't whitelist it to avoid constantly running into auth prompts. 6) authorization can't be re-requested. F.ex. in response to a click on an "Authorize" button in an alert stating that the app lacks a permission. 7) lack of context: users lack usage descriptions to inform their decision. -- What's missing: 1) an API to get the current authorization status without launching the target app, similiar to +[AVCaptureDevice authorizationStatusForMediaType:] or AXIsProcessTrustedWithOptions(). API idea: + (NSAppleEventAuthStatus)authorizationStatusForTarget:(nullable NSAppleEventDescriptor *)eventDesc; "eventDesc" identifies the target app. Passing nil returns the app's whitelist status (see pt. 3). The return value represents the current status: - not determined (user not prompted yet) - denied - authorized 2) an API to prompt without launching the target app, similar to +[AVCaptureDevice requestAccessForMediaType:completionHandler:] or AXIsProcessTrustedWithOptions(). It should be possible to bring up the prompt even if it has been brought up before. API idea: + (void)requestAccessForTarget:(nullable NSAppleEventDescriptor *)eventDesc options:(NSAppleEventAuthOptions)options completionHandler:(void (^)(BOOL granted))handler; "eventDesc" identifies the target app. Passing nil opens System Prefs showing "Automation". "options" allow to show the prompt even if it was shown before. "handler" is called with the result. 3) an option in System Prefs to "whitelist" apps, which can then send AEs to any other app (similar to what the new "Application Data" category achieves for file access). UI ideas: https://bit.ly/2yPoeaW 4) support default and app-specific usage descriptions in prompts. 5) a way for apps to opt out of automatic prompts (as triggered by sending an AE) altogether. Apps that opted out should be returned errAEEventNotPermitted, until the apps brought up the auth prompt (via the API from pt. 2) and won the user's approval. -- Due to length constraints, I couldn't include more examples and details on the suggested APIs in this radar. For these, please see my post on this topic: https://bit.ly/2KeNNaP
 
Next post
Previous post