Applications Scripts denied

Hi all,

I'm developing a sandboxed Mac OS app that generates and compiles AppleScript files to automate tasks in Pages (and other iWork apps). The app creates an AppleScript file and writes it to the NSApplicationScriptsDirectory (i.e., ~/Library/Application Scripts/com.example.app), then compiles and executes it via NSUserAppleScriptTask.

On Mac OS Ventura, however, I get the following error in the console when trying to write the file:

[PagesModifier] Error creating or compiling the script: You are not allowed to save the file "PagesModifier_...applescript" in the folder "com.example.app"

Here are my current entitlements:

<?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.application-groups</key>
<array/>
<key>com.apple.security.automation.apple-events</key>
<array>
<string>com.apple.iWork.Pages</string>
<string>com.apple.iWork.Numbers</string>
<string>com.apple.iWork.Keynote</string>
</array>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.scripting-targets</key>
<dict>
<key>com.apple.iWork.Keynote</key>
<array>
<string>com.apple.iWork.Keynote</string>
</array>
<key>com.apple.iWork.Numbers</key>
<array>
<string>com.apple.iWork.Numbers</string>
</array>
<key>com.apple.iWork.Pages</key>
<array>
<string>com.apple.iWork.Pages</string>
</array>
</dict>
<key>com.apple.security.temporary-exception.apple-events</key>
<array>
<string>com.apple.iWork.Pages</string>
<string>com.apple.iWork.Numbers</string>
<string>com.apple.iWork.Keynote</string>
</array>
<key>com.apple.security.temporary-exception.files.home-relative-path.read-write</key>
<array>
<string>Library/Application Scripts/com.example.app</string>
</array>
</dict>
</plist>

I suspect the issue might be due to sandbox restrictions on dynamically creating or modifying the Application Scripts directory on Ventura. Has anyone experienced something similar or have any suggestions on how to work around this?

Thanks in advance for your help!

Answered by DTS Engineer in 830401022

Thanks for the explanation. And, given that, I think I have an easier path forward for you.

First up, ignore NSUserScriptTask and it’s various subclasses. That infrastructure is designed for script attachment in an App Store app. A great example of that concept is the AppleScript support in Mail’s filtering rules. This is super cool, but it’s not a good match for your app.

Rather, I recommend that you run your scripts using NSAppleScript. If you search the forums, you’ll find that I’ve posted a number of different examples of that tech in the past [1].

This will result in your app sending Apple events to the various iWork apps. By default, those are blocked by the App Sandbox. There are two ways around this:

  • Disable the sandbox completely.

  • Leave the sandbox enabled, but use temporary exception entitlements to open the required holes in the sandbox.

I talk about the second option in more detail in The Case for Sandboxing a Directly Distributed App.

Honestly, if I were you, building an app just for myself, I’d disable the sandbox and continue coding. But the choice is yours, and I’m happy to answer follow-up questions regardless of which path you take.

Share and Enjoy

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

[1] Like here and here.

I just wanna quickly check something here: Are you sandboxing your app because you plan to ship it on the Mac App Store? Or are you planning to distribute your app directly, using Developer ID signing, and are sandboxing your app because it’s the right thing to do?

Share and Enjoy

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

I'm a self-taught beginner, and I understand that sandboxing is considered a good practice. The app I'm developing is just for my personal use. It's meant to manipulate iWork documents for projects that no one else would be interested in, including with GUI Scripting.

Right now, I've worked around the issue by generating a script that opens in Script Editor; I just have to run it, and everything works. However, I would prefer a more elegant solution that is fully managed within my app, without relying on Script Editor.

My app will never be on the Mac App Store. I'm the only one working with my iWork documents for now, but maybe one day I'll have a team, in which case keeping the entire process within my app might become more important.

Thanks for the explanation. And, given that, I think I have an easier path forward for you.

First up, ignore NSUserScriptTask and it’s various subclasses. That infrastructure is designed for script attachment in an App Store app. A great example of that concept is the AppleScript support in Mail’s filtering rules. This is super cool, but it’s not a good match for your app.

Rather, I recommend that you run your scripts using NSAppleScript. If you search the forums, you’ll find that I’ve posted a number of different examples of that tech in the past [1].

This will result in your app sending Apple events to the various iWork apps. By default, those are blocked by the App Sandbox. There are two ways around this:

  • Disable the sandbox completely.

  • Leave the sandbox enabled, but use temporary exception entitlements to open the required holes in the sandbox.

I talk about the second option in more detail in The Case for Sandboxing a Directly Distributed App.

Honestly, if I were you, building an app just for myself, I’d disable the sandbox and continue coding. But the choice is yours, and I’m happy to answer follow-up questions regardless of which path you take.

Share and Enjoy

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

[1] Like here and here.

Thank you, DTS, for your invaluable assistance. I tried to generate and execute an AppleScript directly from the app using NSAppleScript. This script sends Apple Events to a target application (Pages) to modify a document (with and without GUI Scripting). Although Xcode’s console might indicate that the target application isn’t open, it is actually open because non-GUI modifications are still applied correctly.

Here's a generic code:

class GenericAppleScriptRunner {
static func run(script: String) {
if let appleScript = NSAppleScript(source: script) {
var error: NSDictionary?
let output = appleScript.executeAndReturnError(&error)
if let error = error {
print("Error executing AppleScript: \(error)")
} else {
print("AppleScript executed successfully: \(output.stringValue ?? "")")
}
} else {
print("Unable to create NSAppleScript instance.")
}
}
}
let script = """
on robustReplace(theText, searchString, replacementString)
set AppleScript's text item delimiters to searchString
set textItems to every text item of theText
set AppleScript's text item delimiters to replacementString
set newText to textItems as string
set AppleScript's text item delimiters to ""
return newText
end robustReplace
tell application "Pages"
try
set docRef to document "SampleDocument"
on error errDoc
return "Error: " & errDoc
end try
set deletionTags to {"[DELETE_TAG]"}
try
set imagesList to images of docRef
repeat with i from (count of imagesList) to 1 by -1
set img to item i of imagesList
try
set imgDesc to description of img as string
on error
set imgDesc to ""
end try
repeat with tag in deletionTags
if imgDesc contains tag then
delete img
exit repeat
end if
end repeat
end repeat
on error errImages
end try
-- to clean some placeholders
try
set findText to "[FIND_TEXT]"
set replaceText to "[REPLACE_TEXT]"
set bodyText to body text of docRef as string
set newBodyText to my robustReplace(bodyText, findText, replaceText)
set body text of docRef to newBodyText
on error errReplace
end try
activate
end tell
-- Above is processed, below isn't
tell application "System Events"
tell process "Pages"
delay 0.2
keystroke "f" using {command down}
delay 0.2
keystroke "a" using {command down}
-- then follows the process to search & replace with GUI Scripting
end tell
end tell
tell application "Pages"
display dialog "Operation complete" buttons {"OK"} default button "OK"
end tell
"""
GenericAppleScriptRunner.run(script: script)

When I copy the real script to use it directly with ScriptEditor, it does work.

Hmmm, let’s see if you can get any AppleScript working. If you take the script and code from this post, can you get that to work?

Share and Enjoy

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

Without sandbox it does work.

Cool.

Sandboxed: it doesn't work.

OK. You have two paths forward here:

  • Decide not to sandbox your app.

  • Use the Apple event temporary exception entitlement (com.apple.security.temporary-exception.apple-events) to disable that specific sandbox security limit. See App Sandbox Resources for a link to the documentation on that entitlement.

ps It’s better to reply as a reply, rather than in the comments; see Quinn’s Top Ten DevForums Tips for this and other titbits.

Share and Enjoy

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

Applications Scripts denied
 
 
Q