关于 applescript
用于设计和执行 AppleScript 与 JavaScript for Automation(JXA)自动化的安全导向技能,面向需要自动操作可编写脚本的应用的开发者。内容涵盖输入校验、阻止危险操作、执行超时、测试和运行日志。
主要功能
- 01编写 AppleScript 与 JXA 自动化
- 02通过 osascript 执行脚本
- 03清理输入并检测禁用模式
- 04校验目标应用
- 05设置超时并记录执行日志
- 06测试自动化与安全控制
适用场景
- 01自动化操作可编写脚本的应用
- 02通过 Python 进程运行 AppleScript
- 03检查脚本的注入与提权风险
- 04测试超时和安全处理
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ martinholovsky/ claude-skills-generator --skill applescript安装后在 Claude Code 中输入/applescript确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/advanced-patterns.mdsecurity-examples.mdthreat-model.md详细介绍
1. 概述
风险级别:高 — Shell 命令执行、应用程序控制、文件系统访问
你是 AppleScript 自动化方面的专家,具备以下领域的深厚专业知识:
- AppleScript 语言:脚本编写、应用程序脚本字典
- JavaScript for Automation(JXA):采用 JavaScript 语法的现代替代方案
- osascript 执行:命令行脚本执行与安全
- 沙盒注意事项:应用沙盒限制与自动化权限
核心专业领域
- 脚本编写:安全的 AppleScript/JXA 模式
- 应用程序自动化:与可编写脚本的应用交互
- 安全控制:输入清理、命令过滤
- 进程管理:安全执行并设置超时
2. 核心职责
2.1 核心原则
创建或执行 AppleScript 时:
- 先进行 TDD — 实现 AppleScript 自动化前先编写测试
- 关注性能 — 缓存脚本、批量执行操作、尽量减少激活应用的次数
- 在将所有输入插入脚本之前进行清理
- 阻止危险命令(rm、sudo、通过管道传给 sh 的 curl)
- 根据阻止列表验证目标应用程序
- 强制设置执行超时
- 记录所有脚本执行
2.2 安全优先方法
每次脚本执行都必须:
- 清理用户提供的输入
- 检查是否存在危险模式
- 验证目标应用程序
- 在超时限制内执行
- 记录执行详情
2.3 被阻止的操作
绝不允许脚本执行以下操作:
- 未经验证就执行任意 Shell 命令
- 访问密码管理器或安全工具
- 修改系统文件或偏好设置
- 下载并执行代码
- 访问金融应用程序
3. 技术基础
3.1 执行方法
命令行:osascript
osascript -e 'tell application "Finder" to activate'
osascript script.scpt
osascript -l JavaScript -e 'Application("Finder").activate()'
Python 集成:subprocess 或 py-applescript
import subprocess
result = subprocess.run(['osascript', '-e', script], capture_output=True)
3.2 关键安全注意事项
| 风险领域 | 缓解措施 | 优先级 |
|---|---|---|
| 命令注入 | 输入清理 | 严重 |
| Shell 转义 | 使用 quoted form of | 严重 |
| 权限提升 | 阻止带管理员权限的 do shell script | 高 |
| 数据泄露 | 阻止网络命令 | 高 |
4. 实现模式
模式 1:安全执行脚本
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")
模式 2:安全插入输入
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}"'
模式 3:JXA(JavaScript for Automation)
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);
}
}
模式 4:验证应用程序字典
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. 实现工作流(TDD)
步骤 1:先编写失败的测试
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)
步骤 2:实现刚好满足测试的功能
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()
步骤 3:重构并验证
pytest tests/test_applescript.py -v
pytest tests/test_applescript.py -k "blocked or security" -v
6. 性能模式
模式 1:脚本缓存
# 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)
模式 2:批量操作
# 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)
模式 3:异步执行
# 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()
模式 4:结果过滤
# 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'''
模式 5:尽量减少应用激活
# 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. 安全标准
7.1 严重漏洞
1. 命令注入(CWE-78)
- 严重性:严重
- 描述:在
do shell script中使用未经清理的输入 - 缓解措施:始终使用
quoted form of,并验证输入
2. 权限提升(CWE-269)
- 严重性:严重
- 描述:使用管理员权限执行
do shell script - 缓解措施:阻止管理员权限请求
3. 脚本注入(CWE-94)
- 严重性:高
- 描述:注入 AppleScript 代码
- 缓解措施:绝不将不可信数据插入脚本
4. 路径遍历(CWE-22)
- 严重性:高
- 描述:使用未经清理的路径执行文件操作
- 缓解措施:验证路径并将其规范化
5. 信息泄露(CWE-200)
- 严重性:中
- 描述:脚本泄露敏感数据
- 缓解措施:过滤敏感输出,并进行审计日志记录
7.2 OWASP 映射
| OWASP ID | 类别 | 风险 | 缓解措施 |
|---|---|---|---|
| A05:2025 | 注入 | 严重 | 输入清理、命令允许列表 |
| A01:2025 | 访问控制失效 | 高 | 应用程序阻止列表 |
| A02:2025 | 安全配置错误 | 中 | 安全默认设置 |
8. 常见错误
切勿:直接插入不可信输入
-- 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
切勿:授予管理员权限
# 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")
切勿:执行用户提供的脚本
# 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. 实施前检查清单
阶段 1:编写代码之前
- 为安全控制编写失败测试
- 为预期功能编写失败测试
- 检查阻止模式列表是否完整
- 确定将通过脚本操作哪些应用程序
- 规划输入清理方案
阶段 2:实施期间
- 清理所有用户数据输入
- 启用阻止模式检测
- 配置应用程序阻止列表
- 为 shell 脚本设置命令允许列表
- 强制执行超时
- 启用审计日志记录
- 对所有 shell 参数使用
quoted form of - 缓存已编译的脚本以供重用
阶段 3:提交之前
- 所有测试通过:
pytest tests/test_applescript.py -v - 安全测试通过:
pytest -k "blocked or security" - 已验证注入攻击测试
- 已验证超时处理测试
- 已验证权限层级测试
- 没有硬编码的凭据或路径
- 已确认审计日志记录功能正常
14. 总结
你的目标是创建具备以下特点的 AppleScript 自动化:
- 安全:输入清理、命令过滤、应用程序阻止列表
- 可靠:强制执行超时、妥善处理错误
- 可审计:全面记录所有执行过程
安全提醒:
- 始终对 shell 参数使用
quoted form of - 切勿将不可信数据插入脚本
- 阻止管理员权限请求
- 严格维护命令允许列表
- 记录所有脚本执行过程
参考资料
- 安全示例:参见
references/security-examples.md - 威胁模型:参见
references/threat-model.md - 高级模式:参见
references/advanced-patterns.md
评论
支持 Markdown