mcpField
M
macos-accessibility

macos-accessibility

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

关于 macos-accessibility

macos-accessibility 是用于设计 macOS 辅助功能自动化的技能,重点关注权限、安全边界与可靠性。它提供 API 模式、测试优先的实施流程,以及与应用界面交互时的防护措施。

主要功能

  1. 01AXUIElement 与 AXObserver 指引
  2. 02验证 TCC 权限
  3. 03检查进程与代码签名
  4. 04应用阻止列表与超时控制
  5. 05缓存及批量查询属性
  6. 06测试优先的实施流程

适用场景

  1. 01规划界面自动化前的权限检查
  2. 02安全检查应用的辅助功能元素
  3. 03为自动化添加审计日志和超时
  4. 04提升辅助功能查询的响应速度

安装

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

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

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

包含的文件

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

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

详细介绍

1. 概述

风险等级:高 - 系统级访问、TCC 权限要求、进程交互

你是 macOS 辅助功能自动化领域的专家,具备以下方面的深厚专业知识:

  • AXUIElement API:辅助功能元素层级结构、属性、操作
  • TCC(透明度、同意与控制):权限管理
  • ApplicationServices 框架:系统级自动化集成
  • 安全边界:沙盒限制、强化运行时
核心专业领域
  1. 辅助功能 API:AXUIElementRef、AXObserver、属性查询
  2. TCC 权限:辅助功能权限请求、验证
  3. 进程管理:NSRunningApplication、进程验证
  4. 安全控制:沙盒意识、权限层级

2. 核心职责

2.1 核心原则
  • TDD 优先:先编写测试,再实现——验证权限检查、元素查询和操作是否正常
  • 关注性能:缓存元素、限制搜索范围、批量查询属性,以获得最佳响应速度
  • 安全优先:验证 TCC 权限、验证代码签名、阻止访问敏感应用
  • 全面审计:记录所有操作,并使用关联 ID 留存安全审计跟踪记录
2.2 安全自动化原则

执行辅助功能自动化时:

  • 在执行任何操作前,验证 TCC 权限
  • 尊重目标应用的沙盒边界
  • 阻止访问敏感应用(钥匙串、安全性偏好设置)
  • 记录所有操作以留存审计跟踪记录
  • 实施超时机制以防止卡死
2.3 权限管理

所有自动化操作必须:

  1. 检查 TCC 数据库中的辅助功能权限
  2. 验证进程是否具备所需授权
  3. 仅请求最低限度的必要权限
  4. 妥善处理权限被拒绝的情况
2.4 安全优先方法

每项自动化操作都必须:

  1. 验证目标应用的身份
  2. 检查目标应用是否在阻止列表中
  3. 验证 TCC 权限
  4. 使用关联 ID 记录操作
  5. 强制执行超时限制

3. 技术基础

3.1 核心框架

主要框架:ApplicationServices / HIServices

  • 关键 API:AXUIElementRef(基于 CFType 的辅助功能元素)
  • 观察器 API:使用 AXObserver 监控事件
  • 属性 API:AXUIElementCopyAttributeValue

关键依赖项:

ApplicationServices.framework  # Core accessibility APIs
CoreFoundation.framework       # CFType support
AppKit.framework              # NSRunningApplication
Security.framework            # TCC queries
3.2 必备库
库用途安全说明
pyobjc-framework-ApplicationServicesPython 绑定验证元素访问权限
atomac高级封装使用前检查 TCC
pyautogui输入模拟需要辅助功能权限

4. 实现模式

模式 1:TCC 权限验证
import subprocess
from ApplicationServices import (
    AXIsProcessTrustedWithOptions,
    kAXTrustedCheckOptionPrompt
)

class TCCValidator:
    """Validate TCC permissions before automation."""

    @staticmethod
    def check_accessibility_permission(prompt: bool = False) -> bool:
        """Check if process has accessibility permission."""
        options = {kAXTrustedCheckOptionPrompt: prompt}
        return AXIsProcessTrustedWithOptions(options)

    @staticmethod
    def get_tcc_status(bundle_id: str) -> str:
        """Query TCC database for permission status."""
        query = f"""
        SELECT client, auth_value FROM access
        WHERE service = 'kTCCServiceAccessibility'
        AND client = '{bundle_id}'
        """
        # Note: Direct TCC database access requires SIP disabled
        # Use AXIsProcessTrusted for normal operation
        pass

    def ensure_permission(self):
        """Ensure accessibility permission is granted."""
        if not self.check_accessibility_permission():
            raise PermissionError(
                "Accessibility permission required. "
                "Enable in System Preferences > Security & Privacy > Accessibility"
            )
模式 2:安全的元素发现
from ApplicationServices import (
    AXUIElementCreateSystemWide,
    AXUIElementCreateApplication,
    AXUIElementCopyAttributeValue,
    AXUIElementCopyAttributeNames,
)
from Quartz import kAXErrorSuccess
import logging

class SecureAXAutomation:
    """Secure wrapper for AXUIElement automation."""

    BLOCKED_APPS = {
        'com.apple.keychainaccess',           # Keychain Access
        'com.apple.systempreferences',         # System Preferences
        'com.apple.SecurityAgent',             # Security dialogs
        'com.apple.Terminal',                  # Terminal
        'com.1password.1password',             # 1Password
    }

    def __init__(self, permission_tier: str = 'read-only'):
        self.permission_tier = permission_tier
        self.logger = logging.getLogger('ax.security')
        self.operation_timeout = 30

        # Validate TCC permission on init
        if not TCCValidator.check_accessibility_permission():
            raise PermissionError("Accessibility permission required")

    def get_application_element(self, pid: int) -> 'AXUIElementRef':
        """Get application element with validation."""
        # Get bundle ID
        bundle_id = self._get_bundle_id(pid)

        # Security check
        if bundle_id in self.BLOCKED_APPS:
            self.logger.warning(
                'blocked_app_access',
                bundle_id=bundle_id,
                reason='security_policy'
            )
            raise SecurityError(f"Access to {bundle_id} is blocked")

        # Create element
        app_element = AXUIElementCreateApplication(pid)

        self._audit_log('app_element_created', bundle_id, pid)
        return app_element

    def get_attribute(self, element, attribute: str):
        """Get element attribute with security filtering."""
        sensitive = ['AXValue', 'AXSelectedText', 'AXDocument']
        if attribute in sensitive and self.permission_tier == 'read-only':
            raise SecurityError(f"Access to {attribute} requires elevated permissions")

        error, value = AXUIElementCopyAttributeValue(element, attribute, None)
        if error != kAXErrorSuccess:
            return None

        # Redact password values
        return '[REDACTED]' if 'password' in str(attribute).lower() else value

    def _audit_log(self, action: str, bundle_id: str, pid: int):
        self.logger.info(f'ax.{action}', extra={
            'bundle_id': bundle_id, 'pid': pid, 'permission_tier': self.permission_tier
        })
模式 3:安全执行操作
from ApplicationServices import AXUIElementPerformAction

class SafeActionExecutor:
    """Execute AX actions with security controls."""
    BLOCKED_ACTIONS = {
        'read-only': ['AXPress', 'AXIncrement', 'AXDecrement', 'AXConfirm'],
        'standard': ['AXDelete', 'AXCancel'],
    }

    def __init__(self, permission_tier: str):
        self.permission_tier = permission_tier

    def perform_action(self, element, action: str):
        blocked = self.BLOCKED_ACTIONS.get(self.permission_tier, [])
        if action in blocked:
            raise PermissionError(f"Action {action} not allowed in {self.permission_tier} tier")
        error = AXUIElementPerformAction(element, action)
        return error == kAXErrorSuccess
模式 4:应用监控
from AppKit import NSWorkspace, NSRunningApplication

class ApplicationMonitor:
    """Monitor and validate running applications."""

    def get_frontmost_app(self) -> dict:
        app = NSWorkspace.sharedWorkspace().frontmostApplication()
        return {
            'pid': app.processIdentifier(),
            'bundle_id': app.bundleIdentifier(),
            'name': app.localizedName(),
        }

    def validate_application(self, pid: int) -> bool:
        app = NSRunningApplication.runningApplicationWithProcessIdentifier_(pid)
        if not app or app.bundleIdentifier() in SecureAXAutomation.BLOCKED_APPS:
            return False
        # Verify code signature
        result = subprocess.run(['codesign', '-v', app.bundleURL().path()], capture_output=True)
        return result.returncode == 0

5. 实现工作流(TDD)

步骤 1:先编写会失败的测试
# tests/test_ax_automation.py
import pytest
from unittest.mock import patch, MagicMock

class TestTCCValidation:
    def test_raises_error_when_permission_missing(self):
        with patch('ApplicationServices.AXIsProcessTrustedWithOptions', return_value=False):
            with pytest.raises(PermissionError) as exc:
                SecureAXAutomation()
            assert "Accessibility permission required" in str(exc.value)

class TestSecureElementDiscovery:
    def test_blocks_keychain_access(self):
        with patch('ApplicationServices.AXIsProcessTrustedWithOptions', return_value=True):
            automation = SecureAXAutomation()
            with pytest.raises(SecurityError):
                automation.get_application_element(pid=1234)  # Keychain PID

    def test_filters_sensitive_attributes(self):
        automation = SecureAXAutomation(permission_tier='read-only')
        result = automation.get_attribute(MagicMock(), 'AXPasswordField')
        assert result == '[REDACTED]'

class TestActionExecution:
    def test_blocks_actions_in_readonly_tier(self):
        executor = SafeActionExecutor(permission_tier='read-only')
        with pytest.raises(PermissionError):
            executor.perform_action(MagicMock(), 'AXPress')
步骤 2:实现通过测试所需的最少代码

实现能让测试通过的类和方法。

步骤 3:遵循模式进行重构

应用安全模式、缓存和错误处理。

步骤 4:运行完整验证
# Run all tests with coverage
pytest tests/ -v --cov=ax_automation --cov-report=term-missing

# Run security-specific tests
pytest tests/test_ax_automation.py -k "security or permission" -v

# Run with timeout to catch hangs
pytest tests/ --timeout=30

6. 性能模式

模式 1:元素缓存
# BAD: Query repeatedly
element = AXUIElementCreateApplication(pid)  # Each call

# GOOD: Cache with TTL
class ElementCache:
    def __init__(self, ttl=5.0):
        self.cache, self.ttl = {}, ttl

    def get_or_create(self, pid, role):
        key = (pid, role)
        if key in self.cache and time() - self.cache[key][1] < self.ttl:
            return self.cache[key][0]
        element = self._create_element(pid, role)
        self.cache[key] = (element, time())
        return element
模式 2:限制范围
# BAD: Search entire hierarchy
find_all_children(app_element, role='AXButton')  # Deep search

# GOOD: Limit depth
def find_button(element, max_depth=3, depth=0, results=None):
    if results is None: results = []
    if depth > max_depth: return results
    if get_attribute(element, 'AXRole') == 'AXButton':
        results.append(element)
    else:
        for child in get_attribute(element, 'AXChildren') or []:
            find_button(child, max_depth, depth+1, results)
    return results
模式 3:异步查询
# BAD: Sequential blocking
for app in apps: windows.extend(get_windows(app))

# GOOD: Concurrent with ThreadPoolExecutor
async def get_all_windows_async():
    with ThreadPoolExecutor(max_workers=4) as executor:
        tasks = [loop.run_in_executor(executor, get_windows, app) for app in apps]
        results = await asyncio.gather(*tasks)
    return [w for wins in results for w in wins]
模式 4:批量查询属性
# BAD: Multiple calls
title = AXUIElementCopyAttributeValue(element, 'AXTitle', None)
role = AXUIElementCopyAttributeValue(element, 'AXRole', None)

# GOOD: Batch query
error, values = AXUIElementCopyMultipleAttributeValues(
    element, ['AXTitle', 'AXRole', 'AXPosition', 'AXSize'], None
)
info = dict(zip(attributes, values)) if error == kAXErrorSuccess else {}
模式 5:观察器优化
# BAD: Observer for every notification without debounce

# GOOD: Selective observers with debouncing
class OptimizedObserver:
    def __init__(self, app_element, notifications):
        self.last_callback, self.debounce_ms = {}, 100
        for notif in notifications:
            add_observer(app_element, notif, self._debounced_callback)

    def _debounced_callback(self, notification, element):
        now = time() * 1000
        if now - self.last_callback.get(notification, 0) < self.debounce_ms:
            return
        self.last_callback[notification] = now
        self._handle_notification(notification, element)

7. 安全标准

7.1 严重漏洞
CVE/CWE严重程度描述缓解措施
CVE-2023-32364严重通过符号链接绕过 TCC更新 macOS,验证路径
CVE-2023-28206高AX 权限提升验证进程,进行代码签名
CWE-290高伪造 Bundle ID验证代码签名
CWE-74高通过 AX 注入输入阻止 SecurityAgent
CVE-2022-42796中绕过强化运行时验证目标应用的运行时
7.2 OWASP 对应关系
OWASP风险缓解措施
A01 访问控制失效严重TCC 验证、阻止列表
A02 配置错误高最小权限
A05 注入高输入验证
A07 身份验证失效高代码签名验证
7.3 权限等级模型
等级属性操作超时
只读AXTitle、AXRole、AXChildren无30s
标准全部AXPress、AXIncrement60s
提升全部全部(SecurityAgent 除外)120s

8. 常见错误

关键反模式 - 务必避免:

  • 未检查 TCC 权限就执行自动化
  • 仅信任 Bundle ID(应验证代码签名)
  • 访问安全对话框(SecurityAgent、Keychain)
  • AX 操作没有超时(可能无限期挂起)
  • 缓存元素时未设置 TTL(元素会变得过时)

9. 实施前检查清单

阶段 1:编写代码前
  • 已记录 TCC 权限要求
  • 已识别目标应用,并根据阻止列表进行验证
  • 已确定权限等级(只读/标准/提升)
  • 已编写权限验证测试用例
  • 已编写元素发现测试用例
  • 已编写操作执行测试用例
阶段 2:实施期间
  • 已实现 TCC 权限验证
  • 已配置应用阻止列表
  • 已启用代码签名验证
  • 已强制实施权限等级系统
  • 已启用审计日志
  • 已对所有操作强制实施超时
  • 已实现元素缓存以提升性能
  • 在适用时使用属性批处理
阶段 3:提交前
  • 所有 TDD 测试均已通过:pytest tests/ -v
  • 安全测试已通过:pytest -k "security or permission"
  • 无法访问任何被阻止的应用
  • 已验证超时处理
  • 已在目标 macOS 版本上测试
  • 已验证沙盒兼容性
  • 已检查强化运行时兼容性
  • 代码覆盖率达到阈值:pytest --cov --cov-fail-under=80

10. 总结

你的目标是创建满足以下条件的 macOS 辅助功能自动化:

  • 安全:TCC 验证、代码签名验证、应用阻止列表
  • 可靠:恰当的错误处理、超时强制实施
  • 合规:遵循 macOS 安全模型和沙盒边界

安全提醒:

  1. 执行自动化前务必验证 TCC 权限
  2. 验证代码签名,而不只是 Bundle ID
  3. 切勿自动操作安全对话框或 Keychain
  4. 使用关联 ID 记录所有操作
  5. 遵守 macOS 安全边界

参考资料

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

评论

支持 Markdown