Skip to content
linux-at-spi2 logo

tests/test_atspi_automation.py

linux-at-spi2

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. HIGH-RISK skill requiring security controls for system-wide access.

SKILL.md

Full skill instructions

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

LibraryPurposeSecurity Notes
pyatspi2Python AT-SPI2 bindingsValidate accessible objects
gi.repository.AtspiGObject Introspection bindingsCheck object validity
dbus-pythonD-Bus accessUse 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

VulnerabilitySeverityMitigation
AT-SPI2 Registry Bypass (CWE-284)HIGHValidate through registry
D-Bus Session Hijacking (CVE-2022-42012)HIGHValidate D-Bus peer credentials
Password Field Access (CWE-200)CRITICALBlock PASSWORD_TEXT role
Input Injection (CWE-74)HIGHApplication blocklists
Event Flooding (CWE-400)MEDIUMRate 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

More Security skills

supabase logo
Security

supabase

Handles the full Supabase workflow from schema changes to deployment, with built-in security guardrails that catch common traps like RLS...

2.7K 308.9K
View

Comprehensive guides and best practices for Neon Serverless Postgres, covering setup, connection methods, authentication, and platform APIs.

98 216.2K
View

Guide for setting up and using Firebase Authentication. Use this skill when the user's app requires user sign-in, user management, or secure data access using auth rules.

462 163.8K
View

A skill to evaluate how secure Firestore security rules are. Use this when Firestore security rules are updated to ensure that the generated rules are extremely secure and robust.

462 127.7K
View

Official skill for integrating Firebase AI Logic (Gemini API) into web applications. Covers setup, multimodal inference, structured output, and security.

462 125.1K
View

Complete Better Auth server and client setup with database adapters, session management, plugins, and security configuration.

222 118.2K
View

Deploy and manage projects on Vercel using token-based authentication. Use when working with Vercel CLI using access tokens rather than interactive login — e.g. "deploy to vercel", "set up vercel", "add environment variables to vercel".

31.9K 116.1K
View
cloudflare logo
Security

cloudflare

Complete Cloudflare platform integration with decision trees for compute, storage, AI, networking, security, and infrastructure-as-code.

3K 110.7K
View

Run Azure compliance and security audits with azqr plus Key Vault expiration checks. Covers best-practice assessment, resource review, policy/compliance validation, and security posture checks. WHEN: compliance scan, security audit, BEFORE running azqr (compliance cli tool), Azure best practices, Key Vault expiration check, expired certificates, expiring secrets, orphaned resources, compliance assessment.

253 103K
View
gws-gmail logo
Security

gws-gmail

Send, read, and manage Gmail messages, drafts, labels, and account settings.

31.2K 78.9K
View

Comprehensive website auditing across 230+ rules in 21 categories including SEO, performance, security, and accessibility.

94 72.3K
View
gws-shared logo
Security

gws-shared

Shared authentication, CLI syntax, and output formatting patterns for gws Google Workspace commands.

31.2K 63.4K
View

Security AI tools

StarByFace logo
Security

StarByFace

Discover your celebrity doppelgänger with StarByFace!

Free
View
GeoSpy logo
Security

GeoSpy

GeoSpy: Pricing, Features, FAQs, and Alternatives for AI Teams

Free
View

Detect AI-generated voices to protect against audio fraud.

Paid
View

Ensure Your Content's Originality with AI Plagiarism Checker

Freemium
View
Img Upscaler logo
Security

Img Upscaler

Upscale images by 400% without quality loss

Freemium
View
AICheatCheck logo
Security

AICheatCheck

Accurately Detect AI-Generated Content with TheChecker.AI

Free
View
D
Security

Detect GPT

Chrome extension that detects and flags AI-generated content

Free
View