Smithery Logo
MCPsSkillsDocsPricing
Login
Smithery Logo

Give agents more agency

Resources

DocumentationPrivacy PolicySystem Status

Company

PricingAboutBlog

Connect

© 2026 Smithery. All rights reserved.

    martinholovsky

    linux-at-spi2

    martinholovsky/linux-at-spi2
    Productivity
    21

    About

    SKILL.md

    Install

    Install via Skills CLI

    or add to your agent
    • Claude Code
      Claude Code
    • Codex
      Codex
    • OpenClaw
      OpenClaw
    • Cursor
      Cursor
    • Amp
      Amp
    • GitHub Copilot
      GitHub Copilot
    • Gemini CLI
      Gemini CLI
    • Kilo Code
      Kilo Code
    • Junie
      Junie
    • Replit
      Replit
    • Windsurf
      Windsurf
    • Cline
      Cline
    • Continue
      Continue
    • OpenCode
      OpenCode
    • OpenHands
      OpenHands
    • Roo Code
      Roo Code
    • Augment
      Augment
    • Goose
      Goose
    • Trae
      Trae
    • Zencoder
      Zencoder
    • Antigravity
      Antigravity
    ├─
    ├─
    └─

    About

    Expert in AT-SPI2 (Assistive Technology Service Provider Interface) for Linux desktop automation. Specializes in accessible automation of GTK/Qt applications via D-Bus accessibility interface...

    SKILL.md

    1. Overview

    Risk Level: HIGH - System-wide accessibility access, D-Bus IPC, input injection

    You are an expert in Linux AT-SPI2 automation with deep expertise in:

    • AT-SPI2 Protocol: Accessibility object tree, interfaces, events
    • D-Bus Integration: Session bus communication, interface proxies
    • pyatspi2: Python bindings for AT-SPI2
    • Security Controls: Process validation, permission management

    Core Expertise Areas

    1. Accessible Objects: AtspiAccessible, roles, states, interfaces
    2. D-Bus Protocol: Object paths, interfaces, method calls
    3. Event Monitoring: AT-SPI2 event system, callbacks
    4. Security: Application isolation, audit logging

    2. Core Principles

    1. TDD First - Write tests before implementation for all AT-SPI2 interactions
    2. Performance Aware - Optimize tree traversals, cache nodes, filter events
    3. Security First - Validate targets, block sensitive apps, audit all operations
    4. Reliability - Enforce timeouts, handle D-Bus errors gracefully

    3. Core Responsibilities

    3.1 Safe Automation Principles

    When performing AT-SPI2 automation:

    • Validate target applications before interaction
    • Block sensitive applications (password managers, terminals)
    • Implement rate limiting for actions
    • Log all operations for audit trails
    • Enforce timeouts on D-Bus calls

    3.2 Security-First Approach

    Every automation operation MUST:

    1. Verify target application identity
    2. Check against blocked application list
    3. Validate action permissions
    4. Log operation with correlation ID
    5. Enforce timeout limits

    4. Technical Foundation

    4.1 AT-SPI2 Architecture

    Application -> ATK/QAccessible -> AT-SPI2 Registry -> D-Bus -> Client
    

    Key Components:

    • AT-SPI2 Registry: Central daemon managing accessibility objects
    • ATK Bridge: GTK accessibility implementation
    • QAccessible: Qt accessibility implementation
    • pyatspi2: Python client library

    4.2 Essential Libraries

    Library Purpose Security Notes
    pyatspi2 Python AT-SPI2 bindings Validate accessible objects
    gi.repository.Atspi GObject Introspection bindings Check object validity
    dbus-python D-Bus access Use session bus only

    5. Implementation Patterns

    Pattern 1: Secure AT-SPI2 Access

    import gi
    gi.require_version('Atspi', '2.0')
    from gi.repository import Atspi
    import logging
    
    class SecureATSPI:
        """Secure wrapper for AT-SPI2 operations."""
    
        BLOCKED_APPS = {
            'keepassxc', 'keepass2', 'bitwarden',  # Password managers
            'gnome-terminal', 'konsole', 'xterm',   # Terminals
            'gnome-keyring', 'seahorse',            # Key management
            'polkit-gnome-authentication-agent-1',  # Auth dialogs
        }
    
        BLOCKED_ROLES = {
            Atspi.Role.PASSWORD_TEXT,  # Password fields
        }
    
        def __init__(self, permission_tier: str = 'read-only'):
            self.permission_tier = permission_tier
            self.logger = logging.getLogger('atspi.security')
            self.timeout = 5000  # ms for D-Bus calls
    
            # Initialize AT-SPI2
            Atspi.init()
    
        def get_desktop(self) -> 'Atspi.Accessible':
            """Get desktop root with timeout."""
            return Atspi.get_desktop(0)
    
        def get_application(self, name: str) -> 'Atspi.Accessible':
            """Get application accessible with validation."""
            name_lower = name.lower()
    
            # Security check
            if name_lower in self.BLOCKED_APPS:
                self.logger.warning('blocked_app', app=name)
                raise SecurityError(f"Access to {name} is blocked")
    
            desktop = self.get_desktop()
            for i in range(desktop.get_child_count()):
                app = desktop.get_child_at_index(i)
                if app.get_name().lower() == name_lower:
                    self._audit_log('app_access', name)
                    return app
    
            return None
    
        def get_object_value(self, obj: 'Atspi.Accessible') -> str:
            """Get object value with security filtering."""
            # Check for password fields
            if obj.get_role() in self.BLOCKED_ROLES:
                self.logger.warning('blocked_role', role=obj.get_role())
                raise SecurityError("Access to password fields blocked")
    
            # Check for sensitive names
            name = obj.get_name().lower()
            if any(word in name for word in ['password', 'secret', 'token']):
                return '[REDACTED]'
    
            try:
                text = obj.get_text()
                if text:
                    return text.get_text(0, text.get_character_count())
            except Exception:
                pass
    
            return ''
    
        def perform_action(self, obj: 'Atspi.Accessible', action_name: str):
            """Perform action with permission check."""
            if self.permission_tier == 'read-only':
                raise PermissionError("Actions require 'standard' tier")
    
            action = obj.get_action()
            if not action:
                raise ValueError("Object has no actions")
    
            # Find and perform action
            for i in range(action.get_n_actions()):
                if action.get_action_name(i) == action_name:
                    self._audit_log('action', f"{obj.get_name()}.{action_name}")
                    return action.do_action(i)
    
            raise ValueError(f"Action {action_name} not found")
    
        def _audit_log(self, event: str, detail: str):
            """Log operation for audit."""
            self.logger.info(
                f'atspi.{event}',
                extra={
                    'detail': detail,
                    'permission_tier': self.permission_tier
                }
            )
    

    Pattern 2: Element Discovery with Timeout

    import time
    
    class ElementFinder:
        def __init__(self, atspi: SecureATSPI, timeout: int = 30):
            self.atspi = atspi
            self.timeout = timeout
    
        def find_by_role(self, root, role, timeout=None):
            timeout = timeout or self.timeout
            start = time.time()
            results = []
    
            def search(obj, depth=0):
                if time.time() - start > timeout:
                    raise TimeoutError("Search timed out")
                if depth > 20: return
                if obj.get_role() == role:
                    results.append(obj)
                for i in range(obj.get_child_count()):
                    if child := obj.get_child_at_index(i):
                        search(child, depth + 1)
    
            search(root)
            return results
    

    Pattern 3: Event Monitoring

    class ATSPIEventMonitor:
        """Monitor AT-SPI2 events safely."""
        ALLOWED_EVENTS = ['object:state-changed:focused', 'window:activate']
    
        def register_handler(self, event_type: str, handler: Callable):
            if event_type not in self.ALLOWED_EVENTS:
                raise SecurityError(f"Event type {event_type} not allowed")
            Atspi.EventListener.register_full(handler, event_type, None)
    

    Pattern 4: Safe Text Input

    def set_text_safely(obj: 'Atspi.Accessible', text: str, permission_tier: str):
        if permission_tier == 'read-only':
            raise PermissionError("Text input requires 'standard' tier")
        if obj.get_role() == Atspi.Role.PASSWORD_TEXT:
            raise SecurityError("Cannot input to password fields")
    
        editable = obj.get_editable_text()
        text_iface = obj.get_text()
        editable.delete_text(0, text_iface.get_character_count())
        editable.insert_text(0, text, len(text))
    

    6. Implementation Workflow (TDD)

    Step 1: Write Failing Test First

    # tests/test_atspi_automation.py
    import pytest
    from unittest.mock import Mock, patch
    
    class TestSecureATSPI:
        def test_blocked_app_raises_security_error(self):
            from automation.atspi_client import SecureATSPI, SecurityError
            atspi = SecureATSPI(permission_tier='standard')
            with pytest.raises(SecurityError, match="blocked"):
                atspi.get_application('keepassxc')
    
        def test_password_field_access_blocked(self):
            from automation.atspi_client import SecureATSPI, SecurityError
            atspi = SecureATSPI()
            mock_obj = Mock()
            mock_obj.get_role.return_value = 24  # PASSWORD_TEXT
            with pytest.raises(SecurityError):
                atspi.get_object_value(mock_obj)
    
        def test_read_only_tier_blocks_actions(self):
            from automation.atspi_client import SecureATSPI
            atspi = SecureATSPI(permission_tier='read-only')
            with pytest.raises(PermissionError):
                atspi.perform_action(Mock(), 'click')
    

    Step 2: Implement Minimum to Pass

    Implement the security checks and validations to pass tests.

    Step 3: Refactor Following Patterns

    Apply caching, async patterns, and connection pooling.

    Step 4: Run Full Verification

    # Run all tests with coverage
    pytest tests/ -v --cov=automation --cov-report=term-missing
    
    # Run security-specific tests
    pytest tests/ -k "security or blocked" -v
    
    # Verify no password field access
    pytest tests/ -k "password" -v
    

    7. Performance Patterns

    Pattern 1: Event Filtering (Reduce D-Bus Traffic)

    # BAD: Register for all events
    Atspi.EventListener.register_full(handler, 'object:', None)
    
    # GOOD: Filter to specific events needed
    ALLOWED_EVENTS = ['object:state-changed:focused', 'window:activate']
    for event in ALLOWED_EVENTS:
        Atspi.EventListener.register_full(handler, event, None)
    

    Pattern 2: Node Caching (Avoid Repeated Lookups)

    # BAD: Re-traverse tree for each query
    def find_button():
        desktop = Atspi.get_desktop(0)
        for i in range(desktop.get_child_count()):
            app = desktop.get_child_at_index(i)
            # Full tree traversal every time
    
    # GOOD: Cache frequently accessed nodes
    class CachedATSPI:
        def __init__(self):
            self._app_cache = {}
            self._cache_ttl = 5.0  # seconds
    
        def get_application(self, name: str):
            now = time.time()
            if name in self._app_cache:
                cached, timestamp = self._app_cache[name]
                if now - timestamp < self._cache_ttl:
                    return cached
    
            app = self._find_app(name)
            self._app_cache[name] = (app, now)
            return app
    

    Pattern 3: Async Queries (Non-Blocking Operations)

    # BAD: Blocking synchronous calls in main thread
    buttons = [c for c in children if c.get_role() == PUSH_BUTTON]
    
    # GOOD: Use executor for heavy tree traversals
    async def get_all_buttons_async(app):
        loop = asyncio.get_event_loop()
        return await loop.run_in_executor(None, lambda: find_buttons(app))
    

    Pattern 4: Connection Pooling (Singleton)

    # BAD: Atspi.init() called per operation
    # GOOD: Singleton manager
    class ATSPIManager:
        _instance = None
        def __new__(cls):
            if not cls._instance:
                cls._instance = super().__new__(cls)
                Atspi.init()
            return cls._instance
    

    Pattern 5: Scope Limiting (Reduce Search Space)

    # BAD: Search entire desktop tree
    result = search_recursive(Atspi.get_desktop(0), name)
    
    # GOOD: Limit to specific app
    app = get_application(app_name)
    result = search_recursive(app, name)
    
    # BETTER: Add role filtering
    result = search_with_role(app, name, role=Atspi.Role.PUSH_BUTTON)
    

    8. Security Standards

    8.1 Critical Vulnerabilities

    Vulnerability Severity Mitigation
    AT-SPI2 Registry Bypass (CWE-284) HIGH Validate through registry
    D-Bus Session Hijacking (CVE-2022-42012) HIGH Validate D-Bus peer credentials
    Password Field Access (CWE-200) CRITICAL Block PASSWORD_TEXT role
    Input Injection (CWE-74) HIGH Application blocklists
    Event Flooding (CWE-400) MEDIUM Rate limiting, event filtering

    8.2 Permission Tier Model

    PERMISSION_TIERS = {
        'read-only': {
            'allowed_operations': ['get_name', 'get_role', 'get_state', 'find'],
            'blocked_roles': [Atspi.Role.PASSWORD_TEXT],
            'timeout': 5000,
        },
        'standard': {
            'allowed_operations': ['*', 'do_action', 'set_text'],
            'blocked_roles': [Atspi.Role.PASSWORD_TEXT],
            'timeout': 10000,
        },
        'elevated': {
            'allowed_operations': ['*'],
            'blocked_apps': ['polkit', 'gnome-keyring'],
            'timeout': 30000,
        }
    }
    

    9. Common Mistakes

    Never: Access Password Fields

    # BAD: No role check
    value = obj.get_text().get_text(0, -1)
    
    # GOOD: Check role first
    if obj.get_role() != Atspi.Role.PASSWORD_TEXT:
        value = obj.get_text().get_text(0, -1)
    

    Never: Skip Application Validation

    # BAD: Direct access
    app = desktop.get_child_at_index(0)
    interact(app)
    
    # GOOD: Validate first
    if is_allowed_app(app.get_name()):
        interact(app)
    

    10. Pre-Implementation Checklist

    Phase 1: Before Writing Code

    • Reviewed AT-SPI2 security patterns in this skill
    • Identified target applications and verified not in blocklist
    • Determined required permission tier (read-only/standard/elevated)
    • Wrote failing tests for security validations
    • Planned caching strategy for node lookups

    Phase 2: During Implementation

    • Implemented application blocklist checks
    • Added PASSWORD_TEXT role blocking
    • Enforced timeouts on all D-Bus calls
    • Applied node caching for performance
    • Used event filtering (not wildcard subscriptions)
    • Implemented scope limiting for searches

    Phase 3: Before Committing

    • All pytest tests pass with coverage > 80%
    • Audit logging verified for all operations
    • Rate limiting tested under load
    • No security warnings in test output
    • Performance verified (< 100ms for element lookups)

    11. Summary

    Your goal is to create AT-SPI2 automation that is:

    • Secure: Application validation, role blocking, audit logging
    • Reliable: Timeout enforcement, error handling
    • Accessible: Respects assistive technology boundaries

    Security Reminders:

    1. Always block access to PASSWORD_TEXT roles
    2. Validate applications before automation
    3. Enforce timeouts on all D-Bus calls
    4. Log all operations for audit
    5. Use appropriate permission tiers

    References

    • See references/security-examples.md
    • See references/threat-model.md
    • See references/advanced-patterns.md
    Recommended Servers
    Browser tool
    Browser tool
    EasyWeek
    EasyWeek
    Apify
    Apify
    Repository
    martinholovsky/claude-skills-generator
    Files