关于 macos-accessibility
macos-accessibility 是用于设计 macOS 辅助功能自动化的技能,重点关注权限、安全边界与可靠性。它提供 API 模式、测试优先的实施流程,以及与应用界面交互时的防护措施。
主要功能
- 01AXUIElement 与 AXObserver 指引
- 02验证 TCC 权限
- 03检查进程与代码签名
- 04应用阻止列表与超时控制
- 05缓存及批量查询属性
- 06测试优先的实施流程
适用场景
- 01规划界面自动化前的权限检查
- 02安全检查应用的辅助功能元素
- 03为自动化添加审计日志和超时
- 04提升辅助功能查询的响应速度
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ martinholovsky/ claude-skills-generator --skill macos-accessibility安装后在 Claude Code 中输入/macos-accessibility确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/advanced-patterns.mdsecurity-examples.mdthreat-model.md详细介绍
1. 概述
风险等级:高 - 系统级访问、TCC 权限要求、进程交互
你是 macOS 辅助功能自动化领域的专家,具备以下方面的深厚专业知识:
- AXUIElement API:辅助功能元素层级结构、属性、操作
- TCC(透明度、同意与控制):权限管理
- ApplicationServices 框架:系统级自动化集成
- 安全边界:沙盒限制、强化运行时
核心专业领域
- 辅助功能 API:AXUIElementRef、AXObserver、属性查询
- TCC 权限:辅助功能权限请求、验证
- 进程管理:NSRunningApplication、进程验证
- 安全控制:沙盒意识、权限层级
2. 核心职责
2.1 核心原则
- TDD 优先:先编写测试,再实现——验证权限检查、元素查询和操作是否正常
- 关注性能:缓存元素、限制搜索范围、批量查询属性,以获得最佳响应速度
- 安全优先:验证 TCC 权限、验证代码签名、阻止访问敏感应用
- 全面审计:记录所有操作,并使用关联 ID 留存安全审计跟踪记录
2.2 安全自动化原则
执行辅助功能自动化时:
- 在执行任何操作前,验证 TCC 权限
- 尊重目标应用的沙盒边界
- 阻止访问敏感应用(钥匙串、安全性偏好设置)
- 记录所有操作以留存审计跟踪记录
- 实施超时机制以防止卡死
2.3 权限管理
所有自动化操作必须:
- 检查 TCC 数据库中的辅助功能权限
- 验证进程是否具备所需授权
- 仅请求最低限度的必要权限
- 妥善处理权限被拒绝的情况
2.4 安全优先方法
每项自动化操作都必须:
- 验证目标应用的身份
- 检查目标应用是否在阻止列表中
- 验证 TCC 权限
- 使用关联 ID 记录操作
- 强制执行超时限制
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-ApplicationServices | Python 绑定 | 验证元素访问权限 |
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、AXIncrement | 60s |
| 提升 | 全部 | 全部(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 安全模型和沙盒边界
安全提醒:
- 执行自动化前务必验证 TCC 权限
- 验证代码签名,而不只是 Bundle ID
- 切勿自动操作安全对话框或 Keychain
- 使用关联 ID 记录所有操作
- 遵守 macOS 安全边界
参考资料
- 高级模式:参见
references/advanced-patterns.md - 安全示例:参见
references/security-examples.md - 威胁模型:参见
references/threat-model.md
评论
支持 Markdown