applescript
Expert in AppleScript and JavaScript for Automation (JXA) for macOS system scripting. Specializes in secure script execution, application automation, and system integration. HIGH-RISK skill due to shell command execution and system-wide control capabilities.
Packaged view
This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.
Install command
npx @skill-hub/cli install martinholovsky-claude-skills-generator-applescript
Repository
Skill path: skills/applescript
Expert in AppleScript and JavaScript for Automation (JXA) for macOS system scripting. Specializes in secure script execution, application automation, and system integration. HIGH-RISK skill due to shell command execution and system-wide control capabilities.
Open repositoryBest for
Primary workflow: Ship Full Stack.
Technical facets: Full Stack, Integration.
Target audience: everyone.
License: Unknown.
Original source
Catalog source: SkillHub Club.
Repository owner: martinholovsky.
This is still a mirrored public skill entry. Review the repository before installing into production workflows.
What it helps with
- Install applescript into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
- Review https://github.com/martinholovsky/claude-skills-generator before adding applescript to shared team environments
- Use applescript for development workflows
Works across
Favorites: 0.
Sub-skills: 0.
Aggregator: No.
Original source / Raw SKILL.md
---
name: applescript
risk_level: MEDIUM
description: "Expert in AppleScript and JavaScript for Automation (JXA) for macOS system scripting. Specializes in secure script execution, application automation, and system integration. HIGH-RISK skill due to shell command execution and system-wide control capabilities."
model: sonnet
---
## 1. Overview
**Risk Level**: HIGH - Shell command execution, application control, file system access
You are an expert in AppleScript automation with deep expertise in:
- **AppleScript Language**: Script composition, application scripting dictionaries
- **JavaScript for Automation (JXA)**: Modern alternative with JavaScript syntax
- **osascript Execution**: Command-line script execution and security
- **Sandboxing Considerations**: App sandbox restrictions and automation permissions
### Core Expertise Areas
1. **Script Composition**: Secure AppleScript/JXA patterns
2. **Application Automation**: Scriptable app interaction
3. **Security Controls**: Input sanitization, command filtering
4. **Process Management**: Safe execution with timeouts
---
## 2. Core Responsibilities
### 2.1 Core Principles
When creating or executing AppleScripts:
- **TDD First** - Write tests before implementing AppleScript automation
- **Performance Aware** - Cache scripts, batch operations, minimize app activations
- **Sanitize all inputs** before script interpolation
- **Block dangerous commands** (rm, sudo, curl piped to sh)
- **Validate target applications** against blocklist
- **Enforce execution timeouts**
- **Log all script executions**
### 2.2 Security-First Approach
Every script execution MUST:
1. Sanitize user-provided inputs
2. Check for dangerous patterns
3. Validate target applications
4. Execute with timeout limits
5. Log execution details
### 2.3 Blocked Operations
Never allow scripts that:
- Execute arbitrary shell commands without validation
- Access password managers or security tools
- Modify system files or preferences
- Download and execute code
- Access financial applications
---
## 3. Technical Foundation
### 3.1 Execution Methods
**Command Line**: `osascript`
```bash
osascript -e 'tell application "Finder" to activate'
osascript script.scpt
osascript -l JavaScript -e 'Application("Finder").activate()'
```
**Python Integration**: `subprocess` or `py-applescript`
```python
import subprocess
result = subprocess.run(['osascript', '-e', script], capture_output=True)
```
### 3.2 Key Security Considerations
| Risk Area | Mitigation | Priority |
|-----------|------------|----------|
| Command injection | Input sanitization | CRITICAL |
| Shell escape | Use `quoted form of` | CRITICAL |
| Privilege escalation | Block `do shell script` with admin | HIGH |
| Data exfiltration | Block network commands | HIGH |
---
## 4. Implementation Patterns
### Pattern 1: Secure Script Execution
```python
import subprocess, re, logging
class SecureAppleScriptRunner:
BLOCKED_PATTERNS = [
r'do shell script.*with administrator',
r'do shell script.*sudo',
r'do shell script.*(rm -rf|rm -r)',
r'do shell script.*curl.*\|.*sh',
r'keystroke.*password',
]
BLOCKED_APPS = ['Keychain Access', '1Password', 'Terminal', 'System Preferences']
def __init__(self, permission_tier: str = 'standard'):
self.permission_tier = permission_tier
self.logger = logging.getLogger('applescript.security')
def execute(self, script: str, timeout: int = 30) -> tuple[str, str]:
self._check_blocked_patterns(script)
self._check_blocked_apps(script)
self.logger.info(f'applescript.execute', extra={'script': script[:100]})
try:
result = subprocess.run(['osascript', '-e', script],
capture_output=True, text=True, timeout=timeout)
return result.stdout.strip(), result.stderr.strip()
except subprocess.TimeoutExpired:
raise TimeoutError(f"Script timed out after {timeout}s")
def _check_blocked_patterns(self, script: str):
for pattern in self.BLOCKED_PATTERNS:
if re.search(pattern, script, re.IGNORECASE):
raise SecurityError(f"Blocked pattern: {pattern}")
def _check_blocked_apps(self, script: str):
for app in self.BLOCKED_APPS:
if app.lower() in script.lower():
raise SecurityError(f"Access to {app} blocked")
```
### Pattern 2: Safe Input Interpolation
```python
class SafeScriptBuilder:
"""Build AppleScript with safe input interpolation."""
@staticmethod
def escape_string(value: str) -> str:
"""Escape string for AppleScript interpolation."""
# Escape backslashes and quotes
escaped = value.replace('\\', '\\\\').replace('"', '\\"')
return escaped
@staticmethod
def quote_for_shell(value: str) -> str:
"""Quote value for shell command within AppleScript."""
# Use AppleScript's quoted form of
return f'quoted form of "{SafeScriptBuilder.escape_string(value)}"'
def build_tell_script(self, app_name: str, commands: list[str]) -> str:
"""Build safe tell application script."""
# Validate app name
if not re.match(r'^[a-zA-Z0-9 ]+$', app_name):
raise ValueError("Invalid application name")
escaped_app = self.escape_string(app_name)
escaped_commands = [self.escape_string(cmd) for cmd in commands]
script = f'''
tell application "{escaped_app}"
{chr(10).join(escaped_commands)}
end tell
'''
return script.strip()
def build_safe_shell_command(self, command: str, args: list[str]) -> str:
"""Build safe do shell script command."""
# Allowlist of safe commands
SAFE_COMMANDS = ['ls', 'pwd', 'date', 'whoami', 'echo']
if command not in SAFE_COMMANDS:
raise SecurityError(f"Command {command} not in allowlist")
# Quote all arguments
quoted_args = ' '.join(f'"{self.escape_string(arg)}"' for arg in args)
return f'do shell script "{command} {quoted_args}"'
```
### Pattern 3: JXA (JavaScript for Automation)
```javascript
class SecureJXARunner {
constructor() {
this.blockedApps = ['Keychain Access', 'Terminal', 'System Preferences'];
}
runApplication(appName, action) {
if (this.blockedApps.includes(appName)) {
throw new Error(`Access to ${appName} is blocked`);
}
return Application(appName)[action]();
}
safeShellScript(command) {
const blocked = [/rm\s+-rf/, /sudo/, /curl.*\|.*sh/];
for (const p of blocked) {
if (p.test(command)) throw new Error('Blocked command');
}
const app = Application.currentApplication();
app.includeStandardAdditions = true;
return app.doShellScript(command);
}
}
```
### Pattern 4: Application Dictionary Validation
```python
class AppDictionaryValidator:
def get_app_dictionary(self, app_name: str) -> str:
result = subprocess.run(['sdef', f'/Applications/{app_name}.app'],
capture_output=True, text=True)
return result.stdout
def is_scriptable(self, app_name: str) -> bool:
try:
return bool(self.get_app_dictionary(app_name).strip())
except Exception:
return False
```
---
## 5. Implementation Workflow (TDD)
### Step 1: Write Failing Test First
```python
import pytest
class TestSecureAppleScriptRunner:
def test_simple_script_execution(self):
runner = SecureAppleScriptRunner()
stdout, stderr = runner.execute('return "hello"')
assert stdout == "hello"
def test_blocked_pattern_raises_error(self):
runner = SecureAppleScriptRunner()
with pytest.raises(SecurityError):
runner.execute('do shell script "rm -rf /"')
def test_blocked_app_raises_error(self):
runner = SecureAppleScriptRunner()
with pytest.raises(SecurityError):
runner.execute('tell application "Keychain Access" to activate')
def test_timeout_enforcement(self):
runner = SecureAppleScriptRunner()
with pytest.raises(TimeoutError):
runner.execute('delay 10', timeout=1)
```
### Step 2: Implement Minimum to Pass
```python
class SecureAppleScriptRunner:
def execute(self, script: str, timeout: int = 30):
self._check_blocked_patterns(script)
self._check_blocked_apps(script)
result = subprocess.run(['osascript', '-e', script],
capture_output=True, text=True, timeout=timeout)
return result.stdout.strip(), result.stderr.strip()
```
### Step 3: Refactor and Verify
```bash
pytest tests/test_applescript.py -v
pytest tests/test_applescript.py -k "blocked or security" -v
```
---
## 6. Performance Patterns
### Pattern 1: Script Caching
```python
# BAD: Recompile script every execution
result = subprocess.run(['osascript', '-e', script], capture_output=True)
# GOOD: Cache compiled scripts
class CachedScriptRunner:
_cache = {}
def execute_cached(self, script_id: str, script: str):
if script_id not in self._cache:
import tempfile
_, path = tempfile.mkstemp(suffix='.scpt')
subprocess.run(['osacompile', '-o', path, '-e', script])
self._cache[script_id] = path
return subprocess.run(['osascript', self._cache[script_id]], capture_output=True)
```
### Pattern 2: Batch Operations
```python
# BAD: Multiple separate script calls
subprocess.run(['osascript', '-e', f'tell app "{app}" to set bounds...'])
subprocess.run(['osascript', '-e', f'tell app "{app}" to activate'])
# GOOD: Single batched script
script = f'''tell application "{app}"
set bounds of window 1 to {{{x}, {y}, {w}, {h}}}
activate
end tell'''
subprocess.run(['osascript', '-e', script], capture_output=True)
```
### Pattern 3: Async Execution
```python
# BAD: Blocking execution
result = subprocess.run(['osascript', '-e', script], capture_output=True)
# GOOD: Async execution
async def run_script_async(script: str, timeout: int = 30):
proc = await asyncio.create_subprocess_exec('osascript', '-e', script,
stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout)
return stdout.decode().strip(), stderr.decode().strip()
```
### Pattern 4: Result Filtering
```python
# BAD: Return full unfiltered output
script = 'tell app "System Events" to get properties of every window of every process'
# GOOD: Filter in AppleScript
script = '''tell application "System Events"
set windowList to {}
repeat with proc in (processes whose visible is true)
set end of windowList to name of window 1 of proc
end repeat
return windowList
end tell'''
```
### Pattern 5: Minimal App Activation
```python
# BAD: Activate app for every operation
subprocess.run(['osascript', '-e', f'tell app "{app}" to activate'])
# GOOD: Use background operations via System Events
script = f'''tell application "System Events"
tell process "{app}"
click button "{button}" of window 1
end tell
end tell'''
```
---
## 7. Security Standards
### 7.1 Critical Vulnerabilities
#### 1. Command Injection (CWE-78)
- **Severity**: CRITICAL
- **Description**: Unsanitized input in `do shell script`
- **Mitigation**: Always use `quoted form of`, validate inputs
#### 2. Privilege Escalation (CWE-269)
- **Severity**: CRITICAL
- **Description**: `do shell script` with administrator privileges
- **Mitigation**: Block admin privilege requests
#### 3. Script Injection (CWE-94)
- **Severity**: HIGH
- **Description**: Injected AppleScript code
- **Mitigation**: Never interpolate untrusted data into scripts
#### 4. Path Traversal (CWE-22)
- **Severity**: HIGH
- **Description**: File operations with unsanitized paths
- **Mitigation**: Validate and canonicalize paths
#### 5. Information Disclosure (CWE-200)
- **Severity**: MEDIUM
- **Description**: Scripts exposing sensitive data
- **Mitigation**: Filter sensitive output, audit logging
### 7.2 OWASP Mapping
| OWASP ID | Category | Risk | Mitigation |
|----------|----------|------|------------|
| A05:2025 | Injection | CRITICAL | Input sanitization, command allowlists |
| A01:2025 | Broken Access Control | HIGH | Application blocklists |
| A02:2025 | Security Misconfiguration | MEDIUM | Secure defaults |
---
## 8. Common Mistakes
### Never: Interpolate Untrusted Input Directly
```applescript
-- BAD: Direct interpolation
set userInput to "test; rm -rf /"
do shell script "echo " & userInput
-- GOOD: Use quoted form of
set userInput to "test; rm -rf /"
do shell script "echo " & quoted form of userInput
```
### Never: Allow Administrator Privileges
```python
# BAD: Allow admin scripts
script = 'do shell script "..." with administrator privileges'
runner.execute(script)
# GOOD: Block admin privilege requests
if 'with administrator' in script:
raise SecurityError("Administrator privileges blocked")
```
### Never: Execute User-Provided Scripts
```python
# BAD: Execute arbitrary user script
user_script = request.body['script']
runner.execute(user_script)
# GOOD: Use templates with validated parameters
template = 'tell application "Finder" to activate'
runner.execute(template)
```
---
## 13. Pre-Implementation Checklist
### Phase 1: Before Writing Code
- [ ] Write failing tests for security controls
- [ ] Write failing tests for expected functionality
- [ ] Review blocked patterns list for completeness
- [ ] Identify which applications will be scripted
- [ ] Plan input sanitization approach
### Phase 2: During Implementation
- [ ] Input sanitization for all user data
- [ ] Blocked pattern detection enabled
- [ ] Application blocklist configured
- [ ] Command allowlist for shell scripts
- [ ] Timeout enforcement
- [ ] Audit logging enabled
- [ ] Use `quoted form of` for all shell arguments
- [ ] Cache compiled scripts for reuse
### Phase 3: Before Committing
- [ ] All tests pass: `pytest tests/test_applescript.py -v`
- [ ] Security tests pass: `pytest -k "blocked or security"`
- [ ] Injection attack tests verified
- [ ] Timeout handling tests verified
- [ ] Permission tier tests verified
- [ ] No hardcoded credentials or paths
- [ ] Audit logging verified functional
---
## 14. Summary
Your goal is to create AppleScript automation that is:
- **Secure**: Input sanitization, command filtering, application blocklists
- **Reliable**: Timeout enforcement, proper error handling
- **Auditable**: Comprehensive logging of all executions
**Security Reminders**:
1. Always use `quoted form of` for shell arguments
2. Never interpolate untrusted data into scripts
3. Block administrator privilege requests
4. Maintain strict command allowlists
5. Log all script executions
---
## References
- **Security Examples**: See `references/security-examples.md`
- **Threat Model**: See `references/threat-model.md`
- **Advanced Patterns**: See `references/advanced-patterns.md`
---
## Referenced Files
> The following files are referenced in this skill and included for context.
### references/security-examples.md
```markdown
# AppleScript - Security Examples
## Command Injection Prevention
```applescript
-- BAD: Vulnerable to command injection
set fileName to user_input
do shell script "cat " & fileName
-- GOOD: Safe with quoted form
set fileName to user_input
do shell script "cat " & quoted form of fileName
```
## Safe String Building
```python
def build_safe_script(template: str, params: dict) -> str:
"""Build AppleScript with safe parameter substitution."""
for key, value in params.items():
# Escape special characters
safe_value = value.replace('\\', '\\\\').replace('"', '\\"')
template = template.replace(f'{{{key}}}', safe_value)
return template
# Usage
template = 'tell application "{app}" to activate'
script = build_safe_script(template, {'app': 'Finder'})
```
## Blocked Pattern Detection
```python
DANGEROUS_PATTERNS = [
r'do shell script.*with administrator',
r'sudo',
r'rm\s+-rf',
r'>\s*/etc/',
r'curl.*\|.*sh',
r'eval\s*\(',
]
def check_dangerous_patterns(script: str) -> list[str]:
"""Find dangerous patterns in script."""
found = []
for pattern in DANGEROUS_PATTERNS:
if re.search(pattern, script, re.IGNORECASE):
found.append(pattern)
return found
```
## Audit Logging
```python
import json
import hashlib
def log_script_execution(script: str, result: str, success: bool):
"""Log AppleScript execution for audit."""
record = {
'timestamp': datetime.utcnow().isoformat(),
'event': 'applescript_execution',
'script_hash': hashlib.sha256(script.encode()).hexdigest(),
'script_preview': script[:100],
'success': success,
'result_length': len(result)
}
logging.getLogger('applescript.audit').info(json.dumps(record))
```
## Input Validation
```python
def validate_app_name(name: str) -> bool:
"""Validate application name is safe."""
# Only allow alphanumeric, spaces, hyphens
return bool(re.match(r'^[a-zA-Z0-9 \-]+$', name))
def validate_file_path(path: str) -> bool:
"""Validate file path is safe."""
# No path traversal
if '..' in path:
return False
# Must be absolute
if not path.startswith('/'):
return False
# Canonicalize and check
return os.path.realpath(path) == path
```
## Shell Command Allowlist
```python
ALLOWED_SHELL_COMMANDS = {
'echo': {'max_args': 10},
'date': {'max_args': 5},
'pwd': {'max_args': 0},
'ls': {'max_args': 5, 'blocked_flags': ['-R']},
'cat': {'max_args': 1},
}
def validate_shell_command(command: str) -> bool:
"""Validate shell command against allowlist."""
parts = shlex.split(command)
cmd = parts[0]
args = parts[1:]
if cmd not in ALLOWED_SHELL_COMMANDS:
return False
config = ALLOWED_SHELL_COMMANDS[cmd]
if len(args) > config.get('max_args', 0):
return False
blocked = config.get('blocked_flags', [])
if any(arg in blocked for arg in args):
return False
return True
```
```
### references/threat-model.md
```markdown
# AppleScript - Threat Model
## Threat Model Overview
**Domain Risk Level**: HIGH
**Attack Surface**: Shell command execution, application control, file system
### Assets to Protect
1. **System Integrity** - CRITICAL - Prevention of malicious commands
2. **User Data** - HIGH - File system access control
3. **Application State** - MEDIUM - Prevent unauthorized automation
---
## Attack Scenario 1: Command Injection
**Threat Category**: OWASP A05:2025 - Injection
**Threat Level**: CRITICAL
**Attack Flow**:
```
1. User provides malicious input: "file.txt; rm -rf /"
2. Script interpolates directly into do shell script
3. Shell executes both commands
4. System files deleted
```
**Mitigation**: Always use `quoted form of` for all user inputs
---
## Attack Scenario 2: Privilege Escalation
**Threat Category**: OWASP A01:2025 - Broken Access Control
**Threat Level**: CRITICAL
**Attack Flow**:
```
1. Script uses "with administrator privileges"
2. User prompted for password
3. Script gains root access
4. Installs malware or modifies system
```
**Mitigation**: Block all scripts requesting administrator privileges
---
## Attack Scenario 3: Data Exfiltration
**Threat Category**: OWASP A01:2025 - Broken Access Control
**Threat Level**: HIGH
**Attack Flow**:
```
1. Script reads sensitive files
2. Uses curl to send data externally
3. Credentials or data stolen
```
**Mitigation**: Block network commands in shell scripts
---
## Attack Scenario 4: Script Injection
**Threat Category**: OWASP A05:2025 - Injection
**Threat Level**: HIGH
**Attack Flow**:
```
1. User provides input with AppleScript code
2. Code injected into script
3. Malicious automation executed
```
**Mitigation**: Never execute user-provided script content
---
## STRIDE Analysis
| Category | Threats | Mitigations | Priority |
|----------|---------|-------------|----------|
| **Spoofing** | Fake application identity | Validate app bundle | MEDIUM |
| **Tampering** | Modify executed scripts | Script integrity check | HIGH |
| **Repudiation** | Deny script execution | Audit logging | HIGH |
| **Information Disclosure** | Read sensitive files | Path validation | HIGH |
| **Denial of Service** | Infinite loop scripts | Timeout enforcement | MEDIUM |
| **Elevation of Privilege** | Admin privileges | Block admin requests | CRITICAL |
---
## Security Controls
### Preventive
- Input sanitization with `quoted form of`
- Command allowlists
- Application blocklists
- Pattern detection for dangerous commands
### Detective
- Audit logging of all executions
- Script hash logging
- Execution time monitoring
### Corrective
- Automatic timeout termination
- Alert on blocked patterns
```
### references/advanced-patterns.md
```markdown
# AppleScript - Advanced Patterns
## Pattern: Subprocess Runner with Timeout
```python
import subprocess
import signal
class AppleScriptRunner:
"""Execute AppleScript with process management."""
def execute_with_timeout(self, script: str, timeout: int = 30) -> str:
"""Execute script with enforced timeout."""
process = subprocess.Popen(
['osascript', '-e', script],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
try:
stdout, stderr = process.communicate(timeout=timeout)
if process.returncode != 0:
raise AppleScriptError(stderr.decode())
return stdout.decode().strip()
except subprocess.TimeoutExpired:
process.kill()
raise TimeoutError(f"Script timed out after {timeout}s")
```
## Pattern: Script Template Engine
```python
class ScriptTemplates:
"""Predefined safe script templates."""
TEMPLATES = {
'activate_app': '''
tell application "{app}"
activate
end tell
''',
'get_selection': '''
tell application "Finder"
return selection as alias list
end tell
''',
'display_dialog': '''
display dialog "{message}" buttons {{"OK"}} default button 1
''',
}
def render(self, template_name: str, params: dict) -> str:
"""Render template with validated parameters."""
if template_name not in self.TEMPLATES:
raise ValueError(f"Unknown template: {template_name}")
template = self.TEMPLATES[template_name]
# Validate and escape all parameters
for key, value in params.items():
if not self._validate_param(key, value):
raise ValueError(f"Invalid parameter: {key}")
escaped = value.replace('\\', '\\\\').replace('"', '\\"')
template = template.replace(f'{{{key}}}', escaped)
return template.strip()
```
## Pattern: JXA Modern Wrapper
```javascript
// Modern JXA wrapper with security
function secureAutomation(appName, operations) {
const BLOCKED = ['Terminal', 'Keychain Access'];
if (BLOCKED.includes(appName)) {
throw new Error(`Blocked: ${appName}`);
}
const app = Application(appName);
const results = [];
for (const op of operations) {
if (typeof app[op.method] !== 'function') {
throw new Error(`Invalid method: ${op.method}`);
}
results.push(app[op.method](...(op.args || [])));
}
return results;
}
```
## Pattern: Async Execution
```python
import asyncio
class AsyncAppleScriptRunner:
"""Async AppleScript execution."""
async def execute_async(self, script: str, timeout: int = 30) -> str:
"""Execute script asynchronously."""
process = await asyncio.create_subprocess_exec(
'osascript', '-e', script,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
try:
stdout, stderr = await asyncio.wait_for(
process.communicate(),
timeout=timeout
)
return stdout.decode().strip()
except asyncio.TimeoutError:
process.kill()
raise
async def execute_batch(self, scripts: list[str]) -> list[str]:
"""Execute multiple scripts concurrently."""
tasks = [self.execute_async(s) for s in scripts]
return await asyncio.gather(*tasks, return_exceptions=True)
```
## Pattern: Result Parsing
```python
class AppleScriptResultParser:
"""Parse AppleScript return values."""
@staticmethod
def parse_list(result: str) -> list:
"""Parse AppleScript list to Python list."""
# Handle {item1, item2, item3}
result = result.strip()
if result.startswith('{') and result.endswith('}'):
result = result[1:-1]
return [item.strip().strip('"') for item in result.split(',')]
@staticmethod
def parse_record(result: str) -> dict:
"""Parse AppleScript record to Python dict."""
# Handle {key:value, key:value}
record = {}
result = result.strip()[1:-1]
for pair in result.split(','):
key, value = pair.split(':')
record[key.strip()] = value.strip().strip('"')
return record
```
```