Over a weekend recently I built a tiny Mac app (more on that later). What I was trying to achieve required executing AppleScript, like so many things on macOS. It seemed simple enough, but of course new app sandboxing restrictions in macOS Mojave got in the way.
It is surprisingly easy to run AppleScript within a Mac app. All you need to do is create an instance of
NSAppleScript. Swift’s multi-line string literals make it especially nice. You can write your script in the Script Editor, then copy it to your Swift source verbatim. AppleScript can be incredibly powerful and useful. I wrote this one to send my currently open Safari tabs to OmniFocus.
My script was interacting with “System Events”, which provides a lot of miscellaneous system-level functionality. I verified that my script was working as expected with the Script Editor app, but it was not working when executed from within my mac app.
Scripting Additions error
The first error in the Xcode console seemed completely unrelated to my app.
I’m not familiar with “Scripting Additions” but I discovered there were some big changes in Mojave that essentially killed this feature. (Shocking.) As expected, this error was in fact completely unrelated to my app — this file,
Adobe Unit Types.osax, belongs to Adobe. (I have an older version of Photoshop installed.) I don’t know why Xcode displays this error in the console for my app. It seems like it would be simple filter out these irrelevant logs. It makes for a confusing developer experience. Anyway, it was a great way to foreshadow the pain ahead.
System Events isn’t running
The other error in the console was produced by my code. This is contents of printing the
error parameter after calling
Interesting. System Events definitely is running. As far as I know, it is always running. My guess was that this was a permissions issue. It did not take long to find these posts by Daniel Jalkut and Felix Schwarz:
- Apple Events Usage Description
- Reauthorizing Automation in Mojave
- Apple Event sandboxing in macOS Mojave lacks essential APIs
- macOS Mojave gets new APIs around AppleEvent sandboxing — but AEpocalypse still looms
I do not follow the Mac developer scene closely, but it looks like there had been issues with these APIs since the early betas of Mojave. I suggest reading those posts to get the full picture. In short, there are new sandboxing restrictions in Mojave for AppleEvents — the macOS mechanism for automation (like AppleScript) and other communication between applications. Those posts clearly explain the problems and how the new limitations negatively affect developers and users.
Updating plist keys and entitlements
Based on the readings above, the solution was to add the
NSAppleEventsUsageDescription key to my
Info.plist. This is exactly how iOS permissions operate, so it was a familiar solution to me. Unfortunately, the app still did not work. I continued to see the “System Events isn’t running” error.
After some digging around, I learned about the
com.apple.security.temporary-exception.apple-events entitlement, buried in this StackOverflow post. Now I had something to search for in the Mac developer docs, and I found this guide on Temporary Exception Entitlements.
A temporary exception entitlement permits your macOS app to perform certain operations otherwise disallowed by App Sandbox.
If you need to request a temporary exception entitlement, use Apple’s bug reporting system to let Apple know what’s not working for you. Apple considers feature requests as it develops the macOS platform.
There’s an entire section on temporary exceptions for AppleEvents.
However, with App Sandbox you cannot send Apple events to other apps unless you configure a
scripting-targetsentitlement or an
apple-eventstemporary exception entitlement.
scripting-targetsentitlement is the preferred way to request the ability to send Apple events to apps that provide scripting access groups, as described in App Sandbox Entitlement Keys.
I needed to add the following entitlement to my
App.entitlements file. The “temporary exception” in the key name was worrisome. I wondered if this would this be allowed in the Mac App Store? But, now my app worked as expected. Progress!
Discovering scripting targets
Despite my uncertainty around this entitlement, I decided to submit my app to the Mac App Store anyway to see what would happen. (Again, I’ll write more on this specifically in another post.) It was rejected, of course, because that entitlement exception is “not granted by the Apple Core Security team.” Still, I was determined to find a way to make this work. Based on the docs above, it seems like the “App Store approved” entitlement is the
The scripting target entitlement contains a dictionary where each entry has the target app’s code signing identifier as the key, and an array of scripting access groups as the value. Scripting access groups are identified by strings and are specific to an app.
The guide provides this example for the Mail app.
Scripting access groups are provided by applications that support scripting via AppleScript. Access groups define groups of scriptable operations, which you can learn more about in this WWDC talk. There are a couple of ways to discover what is scriptable in an app. You can open the Script Editor, select
File > Open Dictionary..., then select the application you want to automate and explore what is possible. This is great for simply writing AppleScript scripts, but I could not find anything that specified which actions were part of an access group. For that, you need to use the
sdef tool, the scripting definition extractor.
This will dump the specified application’s scripting definition to stdout. I’d recommend tossing the output into a file, so you can open it in an editor.
Once you have this, you can search for
access-group in the definition file. For the Mail app, you will eventually find:
Cool. Now I have a much better understanding of scripting targets and access groups. What’s left is finding out the access groups I need to specify for System Events, so that I can use the approved
And disappointment ensues. The access groups I need are not there. In fact, the only one available is
com.apple.systemevents.window.position, which looks like it was added because of this radar from Craig Hockenberry. About four years later, and no additional access groups have been added.
App sandboxing is too limited
Generally, app sandboxing seems like a good feature to protect users. In practice however, it is extremely frustrating for developers who have to navigate a labyrinth of obscure entitlement options, some of which do not yet exist for functionality we want to provide. And the end results for users are extremely confusing permissions dialogs.
Even the big news about Transmit 5 coming back to the Mac App Store was not that promising considering it is still missing some functionality and required six different kinds of temporary entitlement exceptions, some of which you must request special access to use. Not much of a “win” for developers if you ask me. This was my first real experience trying to write a Mac app after years of doing iOS development and the majority of my time was spent trying to understand how sandboxing works and which entitlements I needed to specify.
I’ll be following up soon with a post about the app.