Théophane
Théophane

Reputation: 73

How to run AppleScript from C++ in macOS sandbox environment without entitlement violations

I am trying to use AppleScript to control the Apple Photos app from within a QT C++ app that must run in the sandbox environment required for the macOS App Store.

I have tried to run the AppleScript via a QProcess launching osascript, like this :

const QString aScript = QString(
        "tell application \"Photos\"\n"
        "    set selMedia to (get media items whose id contains \"%1\")\n"
        "    if not (album \"Trash from %2\" exists) then\n"
        "        make new album named \"Trash from %2\"\n"
        "    end if\n"
        "    add selMedia to album \"Trash from %2\"\n"
        "end tell").arg(fileNameNoExt, APP_NAME);
QProcess script;
script.setProgram("osascript");
script.setArguments(QStringList() <<
                     "-e" << aScript);
script.start();
script.waitForFinished(); 
int exitCode = script.exitCode();
if(exitCode!=0){
    QString outMsg = script.readAllStandardOutput();
    QString errorOut = script.readAllStandardError();
    // Warn user and output message... 
}

This works fine outside the sandbox, adding the desired items from the library to the desired album. However, inside the sandbox, I get an errAEPrivilegeError. Looking in the Console for the error event, it states :

AppleEvents/sandbox: Returning errAEPrivilegeError/-10004 and denying dispatch of event core/getd from process '<private>'/0x0-0x4d84d8, pid=31654, because it is not entitled to send an AppleEvent to this process.

Here is my entitlements file when signing the app :

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>com.apple.security.files.user-selected.read-write</key>
        <true/>
    <key>com.apple.security.app-sandbox</key>
        <true/>
    <key>com.apple.security.scripting-targets</key>
    <dict>
        <key>com.apple.Photos</key>
        <array>
            <string>com.apple.Photos.library.read-write</string>
        </array>
    </dict>
  </dict>
</plist>

When the app is first run (sandboxed) and the AppleScript is started, I get the system permission box, saying that the app is asking to control the Photos app. I click OK of course, but then I get the error. And upon next runs, as the permission is already granted, no system permission box comes up, but it fails nonetheless. When I reset the permissions with the terminal command tccutil reset All [my app bundle id], then upon the next execution of the app and the AppleScript, I get the permission box again.

If I delete the com.apple.security.scripting-targets entitlement, I get no system permission request and the error is different : from my app the error output states Photos isn't running, from the console it says it was denied due to sandboxing. If I keep the scripting-targets and com.apple.Photos but delete library.read-write I get the errAEPrivilegeError again.

I have tried to only test the make new album named "Trash [...]"but this still fails with errAEPrivilegeError. Even if only telling Photos to quit with AppleScript (which according to Photos.app/Contents/Resources/Photos.sdef doesn't require any specific permissions appart from scripting, I also get errAEPrivilegeError. The only command that seems to work is activate.

Also, if I try to run the qprocess not as a separate process, with QProcess::execute (if maybe the sandbox problem is because it is not the main app running the script ?), I still get the same errAEPrivilegeError.

What am I missing for it to work, or what other method should I use to run an applescript from C++/QT when in a sandbox ?

Upvotes: 4

Views: 714

Answers (1)

foo
foo

Reputation: 186

Did you include NSAppleEventsUsageDescription in your app’s Info.plist?

[ETA]

OK, after further digging I can confirm the behavior you’re seeing. With the correct app entitlements an in-process NSAppleScript can send AEs to Photos; however, the same process is unable to send AEs from a child osascript process.

According to Apple docs (which are lousy), a child process must have exactly 2 entitlements to inherit its parent’s: com.apple.security.app-sandbox and com.apple.security.inherit. However, running codesign --display --entitlements - /usr/bin/osascript shows osascript has neither of these but a bunch of unrelated entitlements. So this looks like Apple’s screwup in how they’ve built osascript.

When running AppleScripts that are part of a Swift/ObjC app, I generally recommend using the AppleScript-ObjC bridge to load and call your AppleScript handlers directly; no subprocess required. I have no idea how to do that in a Qt app though.

The simplest solution may be to write your own command-line executable/XPC service in ObjC which runs your AppleScript code via NSAppleScript, giving it the com.apple.security.app-sandbox and com.apple.security.inherit entitlements, and embed it in your Qt app’s main bundle.

(You may also need to add com.apple.security.automation.apple-events in the main app’s entitlements if the runtime is hardened.)

One more thing: avoid generating AppleScript code via string interpolation. If you want to parameterize a script, pack your values as parameters in an Apple event and call the corresponding AS handler via -executeAppleEvent:error:.

Upvotes: 1

Related Questions