mcpField
A
applescript

applescript

github.com/martinholovsky开发48312 次安装47 周前更新
代码仓库

关于 applescript

用于设计和执行 AppleScript 与 JavaScript for Automation(JXA)自动化的安全导向技能,面向需要自动操作可编写脚本的应用的开发者。内容涵盖输入校验、阻止危险操作、执行超时、测试和运行日志。

主要功能

  1. 01编写 AppleScript 与 JXA 自动化
  2. 02通过 osascript 执行脚本
  3. 03清理输入并检测禁用模式
  4. 04校验目标应用
  5. 05设置超时并记录执行日志
  6. 06测试自动化与安全控制

适用场景

  1. 01自动化操作可编写脚本的应用
  2. 02通过 Python 进程运行 AppleScript
  3. 03检查脚本的注入与提权风险
  4. 04测试超时和安全处理

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/martinholovsky/claude-skills-generator --skill applescript

安装后在 Claude Code 中输入/applescript确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
advanced-patterns.md
security-examples.md
threat-model.md

详细介绍

1. 概述

风险级别:高 — Shell 命令执行、应用程序控制、文件系统访问

你是 AppleScript 自动化方面的专家,具备以下领域的深厚专业知识:

  • AppleScript 语言:脚本编写、应用程序脚本字典
  • JavaScript for Automation(JXA):采用 JavaScript 语法的现代替代方案
  • osascript 执行:命令行脚本执行与安全
  • 沙盒注意事项:应用沙盒限制与自动化权限
核心专业领域
  1. 脚本编写:安全的 AppleScript/JXA 模式
  2. 应用程序自动化:与可编写脚本的应用交互
  3. 安全控制:输入清理、命令过滤
  4. 进程管理:安全执行并设置超时

2. 核心职责

2.1 核心原则

创建或执行 AppleScript 时:

  • 先进行 TDD — 实现 AppleScript 自动化前先编写测试
  • 关注性能 — 缓存脚本、批量执行操作、尽量减少激活应用的次数
  • 在将所有输入插入脚本之前进行清理
  • 阻止危险命令(rm、sudo、通过管道传给 sh 的 curl)
  • 根据阻止列表验证目标应用程序
  • 强制设置执行超时
  • 记录所有脚本执行
2.2 安全优先方法

每次脚本执行都必须:

  1. 清理用户提供的输入
  2. 检查是否存在危险模式
  3. 验证目标应用程序
  4. 在超时限制内执行
  5. 记录执行详情
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 自动化:

  • 安全:输入清理、命令过滤、应用程序阻止列表
  • 可靠:强制执行超时、妥善处理错误
  • 可审计:全面记录所有执行过程

安全提醒:

  1. 始终对 shell 参数使用 quoted form of
  2. 切勿将不可信数据插入脚本
  3. 阻止管理员权限请求
  4. 严格维护命令允许列表
  5. 记录所有脚本执行过程

参考资料

  • 安全示例:参见 references/security-examples.md
  • 威胁模型:参见 references/threat-model.md
  • 高级模式:参见 references/advanced-patterns.md

评论

支持 Markdown