mcpField
A
applescript

applescript

github.com/martinholovsky開発48312 回インストール47 週間前に更新
リポジトリ

applescript について

AppleScript と JavaScript for Automation(JXA)の自動化を設計・実行する、セキュリティ重視のスキルです。スクリプト対応アプリを自動操作する開発者向けに、入力検証、危険操作の遮断、タイムアウト、テスト、実行ログを扱います。

主な機能

  1. 01AppleScript と JXA による自動化の作成
  2. 02osascript によるスクリプト実行
  3. 03入力のサニタイズと禁止パターンの検出
  4. 04対象アプリの検証
  5. 05タイムアウト設定と実行ログの記録
  6. 06自動化とセキュリティ制御のテスト

活用シーン

  1. 01スクリプト対応アプリの操作自動化
  2. 02Python プロセスからの 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. 概要

リスクレベル:高 — シェルコマンドの実行、アプリケーションの制御、ファイルシステムへのアクセス

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 ブロック対象の操作

以下を行うスクリプトは決して許可しません。

  • 検証せずに任意のシェルコマンドを実行する
  • パスワードマネージャーやセキュリティツールにアクセスする
  • システムファイルや環境設定を変更する
  • コードをダウンロードして実行する
  • 金融アプリケーションにアクセスする

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 主なセキュリティ上の考慮事項
リスク領域対策優先度
コマンドインジェクション入力のサニタイズ重大
シェルエスケープ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:実装中
  • すべてのユーザーデータをサニタイズする
  • ブロック対象パターンの検出を有効にする
  • アプリケーションのブロックリストを設定する
  • シェルスクリプト用のコマンド許可リストを設定する
  • タイムアウトを適用する
  • 監査ログを有効にする
  • すべてのシェル引数に quoted form of を使用する
  • コンパイル済みスクリプトを再利用できるようにキャッシュする
フェーズ 3:コミット前
  • すべてのテストに合格する:pytest tests/test_applescript.py -v
  • セキュリティテストに合格する:pytest -k "blocked or security"
  • インジェクション攻撃のテストを確認する
  • タイムアウト処理のテストを確認する
  • 権限レベルのテストを確認する
  • 認証情報やパスがハードコードされていない
  • 監査ログが機能することを確認する

14. まとめ

目指すのは、次のような AppleScript オートメーションを作成することです。

  • 安全:入力のサニタイズ、コマンドのフィルタリング、アプリケーションのブロックリスト
  • 信頼性:タイムアウトの適用、適切なエラー処理
  • 監査可能:すべての実行を包括的に記録

セキュリティに関する注意事項:

  1. シェル引数には必ず quoted form of を使用する
  2. 信頼できないデータをスクリプトに埋め込まない
  3. 管理者権限の要求をブロックする
  4. 厳格なコマンド許可リストを維持する
  5. すべてのスクリプト実行を記録する

参考資料

  • セキュリティの例:references/security-examples.md を参照
  • 脅威モデル:references/threat-model.md を参照
  • 高度なパターン:references/advanced-patterns.md を参照

コメント