| name | applescript |
| version | 2.0.0 |
| description | macOS automation with AppleScript and JXA for system scripting, app control, and workflow automation. Use when automating macOS apps, system events, or JXA scripting. Do NOT use for cross-platform scripting (use bash-expert). |
| compatibility | macOS 10.15+ |
| risk_level | MEDIUM |
| token_budget | 3000 |
AppleScript & JXA Expert - Code Generation Rules
0. Anti-Hallucination Protocol
0.2 Security Patterns (security rules)
CWE-78: Command Injection
- Do not:
do shell script userInput
- Instead: Escape with
quoted form of, validate inputs
CWE-732: Permission Escalation
- Do not: Unnecessary
administrator privileges
- Instead: Minimal permissions, prompt only when needed
1. Security Principles
1.1 Script Injection Prevention (CWE-94)
Principle: Never construct AppleScript from untrusted data. Use JXA with proper escaping.
-- ❌ WRONG - Script injection vulnerability
set userInput to "test\" & (do shell script \"rm -rf ~\") & \""
do shell script "echo " & userInput
-- ✅ CORRECT - Use quoted form
set userInput to "untrusted data"
do shell script "echo " & quoted form of userInput
const app = Application.currentApplication();
app.doShellScript(`echo ${userInput}`);
const app = Application.currentApplication();
const escapedInput = userInput.replace(/'/g, "'\\''");
app.doShellScript(`echo '${escapedInput}'`);
1.2 Application Scripting Safety (CWE-20)
Principle: Validate application availability and permissions before automation.
const finder = Application('Finder');
finder.selection();
function getApp(name) {
try {
const app = Application(name);
app.includeStandardAdditions = true;
app.name();
return app;
} catch (e) {
throw new Error(`Cannot access ${name}: ${e.message}`);
}
}
1.3 File Path Validation (CWE-22)
Principle: Validate all paths. Use POSIX paths with proper expansion.
const file = `${userDir}/${filename}`;
function safePath(basePath, filename) {
const ObjC = $.NSString.alloc;
const base = ObjC.initWithString(basePath).stringByStandardizingPath.js;
const full = ObjC.initWithString(`${basePath}/${filename}`)
.stringByStandardizingPath.js;
if (!full.startsWith(base)) {
throw new Error('Path traversal detected');
}
return full;
}
1.4 Secrets ≠ Code (CWE-798)
Principle: Use Keychain for secrets. Never hardcode credentials.
1.5 Privilege Escalation (CWE-269)
Principle: Avoid with administrator privileges unless absolutely necessary.
1.6 Shell Command Safety (CWE-78)
Principle: Always use quoted form of for shell arguments.
2. Version Requirements
Use these minimum versions:
macOS: 10.15+ (Catalina) for JXA stability
osascript: System default
JavaScript for Automation (JXA): Preferred over AppleScript
Recommended: Use JXA (JavaScript for Automation) over AppleScript for better error handling and modern syntax.
3. Code Patterns
3.1 WHEN creating JXA applications
#!/usr/bin/env osascript -l JavaScript
Application('Finder').selection();
function run(argv) {
'use strict';
ObjC.import('Foundation');
const app = Application.currentApplication();
app.includeStandardAdditions = true;
try {
return main(argv);
} catch (e) {
console.log(`Error: ${e.message}`);
return 1;
}
}
function main(argv) {
const config = parseArgs(argv);
if (!config.valid) {
throw new Error(`Invalid arguments: ${config.error}`);
}
return execute(config);
}
function parseArgs(argv) {
if (argv.length < 1) {
return { valid: false, error: 'Missing required argument' };
}
return {
valid: true,
input: argv[0],
};
}
3.2 WHEN automating Finder operations
const finder = Application('Finder');
finder.delete(finder.selection());
function safeFinderOperation(operation) {
const finder = Application('Finder');
finder.includeStandardAdditions = true;
const selection = finder.selection();
if (selection.length === 0) {
throw new Error('No items selected');
}
const items = selection.map(item => {
const path = decodeURI(item.url()).replace('file://', '');
const protectedPaths = ['/System', '/Library', '/usr', '/bin', '/sbin'];
if (protectedPaths.some(p => path.startsWith(p))) {
throw new Error(`Cannot operate on protected path: ${path}`);
}
return { item, path };
});
return items.map(({ item, path }) => {
try {
return operation(finder, item, path);
} catch (e) {
return { path, error: e.message };
}
});
}
const results = safeFinderOperation((finder, item, path) => {
finder.delete(item);
return { path, success: true };
});
3.3 WHEN executing shell commands
function runCommand(input) {
const app = Application.currentApplication();
return app.doShellScript(`echo ${input}`);
}
function safeShellCommand(command, args = [], options = {}) {
const app = Application.currentApplication();
app.includeStandardAdditions = true;
const escapedArgs = args.map(arg => {
if (typeof arg !== 'string') {
throw new Error('Arguments must be strings');
}
return `'${arg.replace(/'/g, "'\\''")}'`;
});
// Build command safely
const fullCommand = [command, ...escapedArgs].join(' ');
// Execute with options
const shellOptions = {};
if (options.asAdmin) {
shellOptions.administratorPrivileges = true;
}
if (options.timeout) {
// JXA doesn't support timeout natively, use timeout command
return app.doShellScript(
`timeout ${options.timeout} ${fullCommand}`,
shellOptions
);
}
return app.doShellScript(fullCommand, shellOptions);
}
// Usage
const output = safeShellCommand('grep', ['-r', searchTerm, directory]);
3.4 WHEN working with Keychain
const password = 'secret123';
function getKeychainPassword(service, account) {
const app = Application.currentApplication();
app.includeStandardAdditions = true;
try {
const cmd = `/usr/bin/security find-generic-password -s '${
service.replace(/'/g, "'\\''")
}' -a '${
account.replace(/'/g, "'\\''")
}' -w`;
return app.doShellScript(cmd);
} catch (e) {
throw new Error(`Keychain access failed: ${e.message}`);
}
}
function setKeychainPassword(service, account, password) {
const app = Application.currentApplication();
app.includeStandardAdditions = true;
// Delete existing entry first (ignore errors)
try {
safeShellCommand('/usr/bin/security', [
'delete-generic-password',
'-s', service,
'-a', account,
]);
} catch (e) {
// Entry may not exist
}
// Add new entry
return safeShellCommand('/usr/bin/security', [
'add-generic-password',
'-s', service,
'-a', account,
'-w', password,
'-U', // Update if exists
]);
}
3.5 WHEN using Objective-C bridge
function processFiles(paths) {
paths.forEach(path => {
const data = $.NSData.dataWithContentsOfFile(path);
});
}
function processFilesSafely(paths) {
ObjC.import('Foundation');
return paths.map(path => {
const pool = $.NSAutoreleasePool.alloc.init;
try {
const nsPath = $.NSString.alloc.initWithUTF8String(path);
const fileManager = $.NSFileManager.defaultManager;
if (!fileManager.fileExistsAtPath(nsPath)) {
return { path, error: 'File not found' };
}
const data = $.NSData.dataWithContentsOfFile(nsPath);
if (data.isNil()) {
return { path, error: 'Could not read file' };
}
const content = $.NSString.alloc
.initWithDataEncoding(data, $.NSUTF8StringEncoding).js;
return { path, content, size: data.length };
} finally {
pool.drain;
}
});
}
3.6 WHEN creating dialogs and user interaction
const app = Application.currentApplication();
const input = app.displayDialog('Enter value:').textReturned;
executeCommand(input);
function getValidatedInput(prompt, validator) {
const app = Application.currentApplication();
app.includeStandardAdditions = true;
const maxAttempts = 3;
for (let i = 0; i < maxAttempts; i++) {
try {
const result = app.displayDialog(prompt, {
defaultAnswer: '',
buttons: ['Cancel', 'OK'],
defaultButton: 'OK',
cancelButton: 'Cancel',
withTitle: 'Input Required',
hiddenAnswer: false,
});
const input = result.textReturned.trim();
const validation = validator(input);
if (validation.valid) {
return validation.value;
}
app.displayAlert('Invalid Input', {
message: validation.error,
as: 'warning',
});
} catch (e) {
if (e.errorNumber === -128) {
return null;
}
throw e;
}
}
throw new Error('Maximum input attempts exceeded');
}
const filename = getValidatedInput('Enter filename:', input => {
if (!input) {
return { valid: false, error: 'Filename cannot be empty' };
}
if (!/^[\w\-. ]+$/.test(input)) {
return { valid: false, error: 'Invalid characters in filename' };
}
if (input.includes('..')) {
return { valid: false, error: 'Path traversal not allowed' };
}
return { valid: true, value: input };
});
4. Anti-Patterns
Do not:
- Use
do shell script without quoted form of
- Construct AppleScript strings from user input
- Use
with administrator privileges unnecessarily
- Store credentials in scripts
- Ignore errors from application calls
- Use
eval() or dynamic script generation
- Access system paths without validation
5. Testing
ALWAYS write tests for automation scripts:
function runTests() {
const tests = [
testSafePathValidation,
testShellCommandEscaping,
testInputValidation,
testApplicationAccess,
# ... (additional test cases follow same pattern)
6. Pre-Generation Checklist
Before generating any AppleScript/JXA code: