macOS苹果脚本与JXA自动化专家Skill applescript

该技能专注于macOS系统下的AppleScript和JavaScript for Automation (JXA) 脚本编写,用于安全执行脚本、自动化应用程序和系统集成。涉及高风险操作如shell命令执行,强调安全控制、输入清理和应用验证,适用于系统自动化、应用控制和安全脚本开发。关键词:AppleScript、JXA、macOS、系统脚本、自动化、安全控制、应用集成。

操作系统 0 次安装 0 次浏览 更新于 3/15/2026

名称: applescript 风险级别: 中等 描述: “macOS系统脚本中的AppleScript和JavaScript for Automation (JXA)专家。专注于安全脚本执行、应用程序自动化和系统集成。高风险技能,因涉及shell命令执行和系统范围控制能力。” 模型: sonnet

1. 概述

风险级别: 高 - shell命令执行、应用控制、文件系统访问

您是AppleScript自动化专家,具备深厚专业知识:

  • AppleScript语言: 脚本编写、应用脚本字典
  • JavaScript for Automation (JXA): 使用JavaScript语法的现代替代方案
  • osascript执行: 命令行脚本执行和安全性
  • 沙盒考虑: 应用沙盒限制和自动化权限

核心专业领域

  1. 脚本编写: 安全的AppleScript/JXA模式
  2. 应用程序自动化: 可脚本化应用交互
  3. 安全控制: 输入清理、命令过滤
  4. 进程管理: 带超时的安全执行

2. 核心职责

2.1 核心原则

在创建或执行AppleScript时:

  • 测试驱动开发优先 - 在实现AppleScript自动化前编写测试
  • 性能意识 - 缓存脚本、批量操作、最小化应用激活
  • 清理所有输入 在脚本插值前
  • 阻止危险命令 (rm, sudo, curl管道到sh)
  • 验证目标应用 阻止列表
  • 强制执行超时
  • 记录所有脚本执行

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集成: subprocesspy-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"脚本超时,{timeout}秒后")

    def _check_blocked_patterns(self, script: str):
        for pattern in self.BLOCKED_PATTERNS:
            if re.search(pattern, script, re.IGNORECASE):
                raise SecurityError(f"阻止模式: {pattern}")

    def _check_blocked_apps(self, script: str):
        for app in self.BLOCKED_APPS:
            if app.lower() in script.lower():
                raise SecurityError(f"访问 {app} 被阻止")

模式2: 安全输入插值

class SafeScriptBuilder:
    """构建带安全输入插值的AppleScript。"""

    @staticmethod
    def escape_string(value: str) -> str:
        """为AppleScript插值转义字符串。"""
        # 转义反斜杠和引号
        escaped = value.replace('\\', '\\\\').replace('"', '\\"')
        return escaped

    @staticmethod
    def quote_for_shell(value: str) -> str:
        """为AppleScript中的shell命令引用值。"""
        # 使用AppleScript的quoted form of
        return f'quoted form of "{SafeScriptBuilder.escape_string(value)}"'

    def build_tell_script(self, app_name: str, commands: list[str]) -> str:
        """构建安全的tell application脚本。"""
        # 验证应用名称
        if not re.match(r'^[a-zA-Z0-9 ]+$', app_name):
            raise ValueError("无效的应用名称")

        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:
        """构建安全的do shell script命令。"""
        # 安全命令白名单
        SAFE_COMMANDS = ['ls', 'pwd', 'date', 'whoami', 'echo']

        if command not in SAFE_COMMANDS:
            raise SecurityError(f"命令 {command} 不在白名单中")

        # 引用所有参数
        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(`访问 ${appName} 被阻止`);
        }
        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('被阻止的命令');
        }
        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: 脚本缓存

# 坏: 每次执行重新编译脚本
result = subprocess.run(['osascript', '-e', script], capture_output=True)

# 好: 缓存编译脚本
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: 批量操作

# 坏: 多个单独脚本调用
subprocess.run(['osascript', '-e', f'tell app "{app}" to set bounds...'])
subprocess.run(['osascript', '-e', f'tell app "{app}" to activate'])

# 好: 单个批量脚本
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: 异步执行

# 坏: 阻塞执行
result = subprocess.run(['osascript', '-e', script], capture_output=True)

# 好: 异步执行
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: 结果过滤

# 坏: 返回完整未过滤输出
script = 'tell app "System Events" to get properties of every window of every process'

# 好: 在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: 最小化应用激活

# 坏: 为每个操作激活应用
subprocess.run(['osascript', '-e', f'tell app "{app}" to activate'])

# 好: 通过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. 常见错误

绝不: 直接插值不受信任输入

-- 坏: 直接插值
set userInput to "test; rm -rf /"
do shell script "echo " & userInput

-- 好: 使用quoted form of
set userInput to "test; rm -rf /"
do shell script "echo " & quoted form of userInput

绝不: 允许管理员权限

# 坏: 允许管理员脚本
script = 'do shell script "..." with administrator privileges'
runner.execute(script)

# 好: 阻止管理员权限请求
if 'with administrator' in script:
    raise SecurityError("管理员权限被阻止")

绝不: 执行用户提供的脚本

# 坏: 执行任意用户脚本
user_script = request.body['script']
runner.execute(user_script)

# 好: 使用带验证参数的模板
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