How to enable AppleScript between Parent and Child applications within sandbox for Mac App Store?

I have a parent app that contains a child app in the bundle. The child app uses applescript to trigger some events in the parent app.

Now that I'm preparing for App Store I have sandboxed the apps and I am missing something with the entitlements that I don't understand.

In the parent app I have defined the .sdef and I can dump it with sdef /Applications/Parent.app once installed from TestFlight and I see:


<!DOCTYPE dictionary SYSTEM "file://localhost/System/Library/DTDs/sdef.dtd">

<dictionary title="Parent">

    <suite name="Parent Suite" code="prnt" description="Parent Scripts">

        <command name="is_first_run" code="prntisfr" description="Is this the first time running the app?">
            <cocoa class="MyScriptInterface"/>
        </command>

        <command name="activation_complete" code="prntWRac" description="Activation is complete">
            <cocoa class="MyScriptInterface"/>
        </command>

        <command name="sign_out" code="prntWRso" description="Sign out and delete local credentials">
            <cocoa class="MyScriptInterface"/>
        </command>

        <command name="get_version" code="prntgetv">
            <cocoa class="MyScriptInterface"/>
            <direct-parameter type="text" description="None"/>
            <result type="text"/>
        </command>

    </suite>

</dictionary>

In the parent app I have the following included in the .plist:

<key>NSAppleEventsUsageDescription</key>

        <string>AppleEvents needed to communicate between components</string>

        <key>NSAppleScriptEnabled</key>

        <true/>
        <key>OSAScriptingDefinition</key>

        <string>Parent.sdef</string>

In the Parent entitlements I include the following key parts:

<key>com.apple.security.automation.apple-events</key>
        <true/>
<key>com.apple.security.scripting-targets</key>
        <dict>
                <key>com.foo.parent</key>
                <array>
                        <string>com.foo.parent.is_first_run</string>
                     <string>com.foo.parent.activation_complete</string>
                        <string>com.foo.parent.sign_out</string>
                        <string>com.foo.parent.get_version</string>
                </array>

                <key>com.foo.parent.child</key>
                <array>
                        <string>com.foo.parent.is_first_run</string>
                        <string>com.foo.parent.activation_complete</string>
                        <string>com.foo.parent.sign_out</string>
                        <string>com.foo.parent.get_version</string>
                </array>

        </dict>

In the Child app I have only this key:

<key>NSAppleScriptEnabled</key>
        <true/>

And in the Child entitlements I have only this:

<?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.app-sandbox</key>
        <true/>

        <key>com.apple.security.inherit</key>
        <true/>
</dict>
</plist>

I am not getting something. When I install the app and watch Console output I see a combination of errors:

For the Parent app:

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

For the Child app from sandboxd:


Violation:       deny(1) appleevent-send com.apple.systemevents

MetaData: {"platform-binary":false,"build":"macOS 12.3.1 (21E258)","sandbox_checker":"appleeventsd","process-path":"\/Applications\/Parent.app\/Contents\/MacOS\/Child.app\/Contents\/MacOS\/Child","profile-in-collection":false,"platform_binary":"no","primary-filter-value":"com.apple.systemevents","primary-filter":"appleevent-destination","checker":"appleeventsd","platform-policy":false,"policy-description":"Sandbox","summary":"deny(1) appleevent-send com.apple.systemevents","binary-in-trust-cache":false,"responsible-process-team-id":"367******2","target":"com.apple.systemevents","hardware":"Mac","pid":95806,"appleevent-destination":"com.apple.systemevents","flags":5,"responsible-process-signing-id":"com.foo.parent","apple-internal":false,"normalized_target":["com.apple.systemevents"],"checker-pid":359,"profile-flags":0,"operation":"appleevent-send","errno":1,"action":"deny","uid":501,"responsible-process-path":"\/Applications\/Parent.app\/Contents\/MacOS\/Parent","signing-id":"com.foo.parent.child","team-id":"367******2","container":"\/Users\/spartygw\/Library\/Containers\/com.foo.parent\/Data","process":"Child","release-type":"User"}
  • I should've included how I'm attempting to execute applescript from the Child app:

    NSDictionary* errorDict; NSAppleScript* scriptObject = [[NSAppleScript alloc] initWithSource: @"tell application id \"com.foo.parent\" to sign_out"]; NSAppleEventDescriptor* returnDescriptor = [scriptObject executeAndReturnError: &errorDict]; Maybe I can only execute compiled applescripts?

Add a Comment

Replies

Apple Script used to be completely forbidden in the Mac App Store. From what I understand, there is some limited usage available with Mail and possibly 3rd party apps. You seem to be doing everything correctly according to my limited understanding of the documentation.

However, your console output specifically says "com.apple.systemevents", which would definitely be a forbidden target. Maybe it would work if you tried the scripting bridge directly.

Why bother with AppleScript at all anyway? There are lots more convenient ways to communicate between a parent and helper, such as XPC.

  • I'm no expert but I thought the additions to <key>com.apple.security.scripting-targets</key> explicitly call out applescript targets is what the AppStore requires. As for why am I using AS at all? Well I could revisit it but it works great and this code has been distributed to customers with Developer ID-signed installers. I'm tasked with getting this on the App Store and I'm trying to touch as little code as possible.

  • But that's the entire point. It sounds like you are doing things correctly. Yet it isn't working. You are going to have to develop additional expertise to figure that out. Let's go ahead and assume you fix it. What value does that additional knowledge of AppleScript edge cases in the Mac App Store bring to your app, your employer, and to you personally? You are just paying the minimum monthly payment on your technical debt and kicking the can down the road to be someone else's problem later.

Add a Comment

I have a parent app that contains a child app in the bundle. The child app uses applescript to trigger some events in the parent app.

In this setup how is the child app started?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

  • `         let process = Process()         let url = Bundle.main.bundleURL.path + "/Contents/MacOS/Child.app/Contents/MacOS/Child"

            process.executableURL = URL(fileURLWithPath:url)         process.terminationHandler = { (process) in            print("\ndidFinish: (!process.isRunning)")         }         do {           try process.run()         } catch let error as NSError {             NSLog("Failed to execute ", error)         } `

Add a Comment

I tried to post a comment to Quinn's question but the code formatting for comments is ugly. Here is how I'm starting the child app:

let process = Process()
let url = Bundle.main.bundleURL.path + "/Contents/MacOS/Child.app/Contents/MacOS/Child"

process.executableURL = URL(fileURLWithPath:url)
process.terminationHandler = { (process) in
    print("\ndidFinish: \(!process.isRunning)")
}
do {
    try process.run()
} catch let error as NSError {
    NSLog("Failed to execute ", error)
}
  • Normally helpers are in a "Helpers" folder alongside the "MacOS" folder. I don't know if that's the cause of the problem. It shouldn't even matter as far as AppleScript is concerned. It doesn't care if the target is running or not. It will launch it if it needs to. In this case however, Mac App Store security restrictions might break something that was already broken, yet working by accident, outside of the Mac App Store environment.

Add a Comment

I tried to post a comment to Quinn's question but the code formatting for comments is ugly.

Yep. Sorry about that. Comments aren’t meant for full-fledged replies, despite the UI misleading you into thinking that they are )-: This is something that we intend to fix (r. 80839588).

Here is how I'm starting the child app:

Oh, interesting, it really is a child process.

Note Hardcoding /Contents/MacOS/Child.app/Contents/MacOS/Child is kinda bogus. I’ve posted a better alternative below. However, I can guarantee you that this isn’t the cause of your Apple event problem.

I’d like to take a step back and ask why you’re using Apple events for this IPC? There are plenty of other IPC options and combining Apple events and sandboxing is tricky.


nk_kennedy wrote:

Normally helpers are in a Helpers folder alongside the MacOS folder.

That’s one option, but storing helpers in Contents/MacOS is also allowed. For more about this, see Placing Content in a Bundle.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"


To find the main executable of a helper app:

  1. Call the url(forAuxiliaryExecutable:) method on the main bundle to get a url for helper app.

  2. Construct a bundle from that URL.

  3. Use the executableURL property to get the helper app’s executable.

  • Given the time already spent on this and the only two people who've replied to me both suggesting a design change I will go ahead and start reading about XPC. Thank you for your time.

Add a Comment

I will go ahead and start reading about XPC.

So that’s tricky as well, because XPC does not have good support for parent/child IPC )-:

Can you explain a bit more about your IPC needs? In your first post you had this:

<string>com.foo.parent.is_first_run</string>
<string>com.foo.parent.activation_complete</string>
<string>com.foo.parent.sign_out</string>
<string>com.foo.parent.get_version</string>

which gives me some idea as to the scope of this problem. Can you expand on that?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

  • Absolutely. The parent app is a persistent app that presents an icon and menu in the toolbar and is doing some tasks in the background. It's responsible for handling user activation and sign out with a server. The child process is a GUI that is started from the toolbar and presents the user with a control panel of sorts. The child app needs to know things that only the Parent app knows. So I'm achieving that right now with a simple AppleScript interface for me to query the parent. It works great. I hate that I might have to build an entire XPC service and framework for the few comms I need to support. My 1 day of just perusing XPC docs seems like it's more for doing one-off tasks than IPC but I haven't spent a lot of time yet. The daily tasks of the job have me busy.

Add a Comment

The parent app is a persistent app that presents an icon and menu in the toolbar

I’m like the clarify that last bit. What toolbar are you talking about here? The right side of the main menu bar? Using APIs like NSStatusItem and NSStatusBar?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"