Avoiding AppleScript Security and Privacy Requests

AppleScript on macOS is a useful tool for pro users and administrators alike. Even though it probably is not (and shouldn’t be) the first tool of choice for many tasks, there are some tasks that AppleScript makes very simple. Because of this it should be a part of your ‘MacAdmin Toolbelt.’

AppleScript’s strength lies in inter-application communication. With AppleEvents (or AppleScript commands) you can often retrieve valuable information from other applications that would be difficult or even impossible, to get any other way. With AppleScript, you may even be able to create and change data in the target applications.

If you are in any way security and privacy minded this should raise your hairs. Up to macOS 10.13 High Sierra, any non-sandboxed app could use AppleScript and AppleEvents to gather all kinds of personal and private data from various script-enabled apps and services. It could even use script-enabled apps like Mail to create and send email in your name.

Since macOS Mojave, the Security and Privacy controls restricts sending and receiving AppleEvents. A given process can only send events to a different process with user approval. Users can manage the inter-application approvals in the Privacy tab of the Security & Privacy preference pane.

MacAdmins have the option of pre-approving inter-application events with a PPPC (Privacy Preferences Policy Control) configuration profile that is pushed from a DEP-enrolled or user-approved MDM.

Privacy approval

You can trigger the security approval from Terminal when you send an event from the shell to another process with osascript:

> osascript -e 'tell application "Finder" to get POSIX path of ((target of Finder window 1) as alias)'

When you run this command from Terminal, you will likely get this prompt:

You will not get this prompt when you have approved or rejected the Terminal app to send events to this particular target application before. You can check the permissions granted by the user in the Automation section of Privacy tab in the Security & Privacy pane of System Preferences.

For any given source/target application combination, the prompt will only be shown once. When the user approves the privilege (“OK” button), future events will just be allowed.

When the user rejects the connection (“Don’t Allow” button), this event and future events will be rejected without further prompts. The osascript will fail and the AppleScript will return an error –1743.

> osascript -e 'tell application "Finder" to get POSIX path of ((target of Finder window 1) as alias)'
79:84: execution error: Not authorized to send Apple events to Finder. (-1743)

If you want to get the approval dialogs again, you can reset the state of the source application (Terminal) with the tccutil command:

> tccutil reset AppleEvents com.apple.Terminal

This will remove the Terminal application and all target applications for it from the Automation (AppleEvents) area in the Privacy pane and show dialogs for every new request going forward. This can be very useful during testing.

Dealing with rejection

You should write your code in a ways that it fails gracefully when access is not granted. in this case osascript will return an error:

if ! osascript -e ' tell app "Finder" to return POSIX path of ((target of Finder window 1) as alias)'
then
 echo "osascript encountered an error"
 exit 1
fi

However, osascript will return errors for all kind of failures with no easy way to distinguish between them. As an example, the above will also fail when there are no Finder windows open.

If you want to distinguish AppleScript errors, you need to do so in the the AppleScript code:

if ! osascript -s o <<EndOfScript
    tell application "Finder"
        try
            set c to (count of Finder windows)
        on error message number -1743
            error "Privacy settings prevent access to Finder"
        end try
        
        if c is 0 then
            return POSIX path of (desktop as alias)
        else
            return POSIX path of ((target of Finder window 1) as alias)
        end if
    end tell
EndOfScript
then
    echo "osascript failed"
fi

Note: the -s o option of osascript makes it print AppleScript errors to standard out rather than standard error, which can be useful to find the errors in logs of management systems.

Note 2: when you are running osascript from management and installation scripts (which run as the root user) you need to run them as the current user to avoid problems.

Avoiding Privacy prompts

So, we know of one way to deal with the privacy prompts. Ideally, you would want to avoid them entirely. While this is not always possible, there are a few strategies that can work.

Don’t send to other Processes

In past versions of Mac OS X (I use this name intentionally, it’s that long ago.), scripts that showed dialogs might not display on the highest window layer. In other words, the dialog was lost behind the currently active windows. To avoid “lost” dialogs, it became best practice to send the display dialog command (and similar) to a process that had just received an activate command as well:

tell application "Finder"
    activate
    display dialog "Hello, World!"
end tell

As an alternative for Finder, the System Events process is often used as well. Jamf MacAdmins often used “Self Service.” This had the added bonus, that the dialog looks as if it comes from the Finder or Self Service, including the bouncing dock icon.

Over time, even though the underlying problem with hidden dialog has been fixed, this practice has persisted. You often even see AppleScript code use this with commands other than user interaction, where it wouldn’t have made sense in the first place. With the privacy restrictions in macOS Mojave, this practice has become actively trouble some, as you are sending the display dialog (or other) command to a separate process. The process running this script will require approval to send events to “System Events.”

osascript <<EndOfScript
    tell application "System Events"
        activate
        display dialog "Hello, World!"
    end tell
EndOfScript

In current versions of macOS, you can just use display dialog and may other commands without an enclosing tell block. Since your AppleScript code isn’t sending events to another process, no privacy approval is provided. This code has the same effect as above, but does not trigger an approval request.

osascript <<EndOfScript
    display dialog "Hello, World!"
EndOfScript

To determine whether an AppleScript command requires a tell block, you have to check where it is coming from. Many AppleScript commands that are useful to MacAdmins are contained in the ‘StandardAdditions’ scripting extension. Scripting extensions, as the name implies, extend the functionality of AppleScript without requiring their own process.

The useful commands in the Standard Additions extension include:

  • user interaction: choose file/folder/from list, display dialog/alert/notification
  • file commands: mount volume
  • clipboard commands: get the clipboard, set the clipboard to
  • sound control: set volume, get volume settings
  • system info

When your script uses only these commands, make sure they are not contained in tell blocks. This will avoid unnecessary prompts for access approval.

Exempt AppleScript commands

Some AppleScript commands are treated differently and will not trigger privacy approval:

  • activate: launch application and/or bring to front
  • open: open a file
  • open location: open a URL
  • quit: quit the application

For example, this will work without requiring approval:

osascript <<EndOfScript
    tell application "Firefox"
        open location "https://scriptingosx.com"
    end
EndOfScript

Use non-AppleScript alternatives

Sometimes, similar effects to an AppleScript can be achieved through other means. This can be difficult to figure out and implement.

As an example, I used this AppleScript command frequently for setup before Mojave:

tell application "Finder" to set desktop picture to POSIX file "/Library/Desktop Pictures/BoringBlueDesktop.png"

While Mojave was in the beta and it wasn’t really clear if or how the PPPC exemptions could be managed, I looked for a different means. I discovered Cocoa functions to read and change the desktop picture without triggering PPPC, and built a small command line tool out of that: desktoppr.

The downside of this approach is that you know have to install and/or manage a command line tool on the clients where you want to use it. There are different strategies for this, but it is extra effort compared to “just” running an AppleScript.

Build PPPC profiles to pre-approve AppleEvents

Even after you have considered the above options to avoid sending AppleEvents to another process, there will still be several situations where it is necessary. For situations where a MacAdmin needs to run a script on several dozens, hundreds, or even thousands of Macs, user-approval is simply not a feasible option.

MacAdmins can pre-approve AppleEvents (and most other privacy areas) between certain processes with a Privacy Preferences Policy Control (PPPC) configuration profile. PPPC profiles can only be managed when pushed from a user-approved or automatically enrolled MDM.

You can build such a profile manually, but it is much easier to use a tool to build these:

Your MDM solution might have a specific tool or web interface for this, consult the documentation or ask you vendor.

There is one big requirement here, though: only applications and tools that are signed with a valid Apple Developer ID can be pre-approved this way, as the signature is used to identify and verify the binary.

Determining the process that needs approval

While you can sign shell scripts and other scripts this is often not necessary. As we have seen earlier, when we ran our script from Terminal, it wasn’t the script that requested approval but the Terminal application. When your scripts run from a management system or another tool, it may not be easy to determine which process exactly needs approval.

The most practical approach to determine this, is to log the output of the ’Transparency, Consent, and Control” system (tcc) and look which process is sending the requests.

First, either use a clean test system, or reset the approvals for the processes that you suspect may be involved with tccutil.

Then open a separate Terminal window and run this command which will show a stream of log entries from the tcc process:

> log stream --debug --predicate 'subsystem == "com.apple.TCC" AND eventMessage BEGINSWITH "AttributionChain"'

There will be a lot of noise in this output.

Then run the script in question, the way you are planning to run it during deployment. If you are planning to run the script from a management system, then do that right now. You will get a lot output in the stream above.

Even when you don’t have a good idea what the parent process is going to be, you can filter the output for osascript since this is usually the intermediary tool used.

In my example I found several entries similar to this:

   0    tccd: [com.apple.TCC:access] AttributionChain: RESP:{ID: com.barebones.bbedit, PID[1179], auid: 501, euid: 501, responsible path: '/Applications/BBEdit.app/Contents/MacOS/BBEdit', binary path: '/Applications/BBEdit.app/Contents/MacOS/BBEdit'}, ACC:{ID: com.apple.osascript, PID[18756], auid: 501, euid: 501, binary path: '/usr/bin/osascript'}, REQ:{ID: com.apple.appleeventsd, PID[577], auid: 55, euid: 55, binary path: '/System/Library/CoreServices/appleeventsd'}

The important information here is the responsible path which give me the binary and the enclosing application that tcc considers ‘responsible.’ This is the application you need to approve.

When you are running your scripts from a management system, your MDM vendor/provider should already have documentation for this, to save you all this hassle.

With all this information, you can build the PPPC profile with one of the above tools, upload it to your MDM and push it to the clients before the deployment scripts run.

Conclusion

While the added privacy around AppleEvents is welcome, it does add several hurdles to automated administration workflows.

There are some strategies you can use to avoid AppleScripts triggering the privacy controls. When these are not sufficient, you have to build a PPPC profile to pre-approve the parent process.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.