Back to skill

Security audit

dekstop-control-linux

Security checks for vulnerabilities and agentic risk

Overview

This Linux desktop automation skill is coherent, but its advertised approval mode does not cover several sensitive actions like screenshots, screen recording, clipboard access, OCR, and some desktop manipulation.

Review before installing. This skill gives an agent practical control over a Linux desktop session and can capture visible screen contents, OCR text, read/write clipboard data, record video, launch apps, and save files. Use it only in a dedicated, non-sensitive desktop session, avoid disabling approval mode, and treat screenshots or recordings as sensitive data.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
Findings (3)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
__init__.py:149
Finding
Approval Mode Does Not Protect Multiple Sensitive Desktop Operations<![CDATA[ ## Vulnerability Details **File Location**: `__init__.py`, lines 149–156, 396–405, 677–710, and 790–803 **Vulnerability Type**: Incomplete authorization enforcement **Risk Level**: High ### Vulnerable Code ```python def scroll(self, clicks: int, direction: str = "vertical", x: Optional[int] = None, y: Optional[int] = None) -> None: if x is not None and y is not None: pyautogui.moveTo(x, y) if direction == "vertical": pyautogui.scroll(clicks) else: pyautogui.hscroll(clicks) ``` ```python def drag_drop(self, from_x: int, from_y: int, to_x: int, to_y: int, duration: float = 0.5) -> None: """Drag from (x1, y1) to (x2, y2).""" pyautogui.moveTo(from_x, from_y, duration=0.2) pyautogui.mouseDown() pyautogui.moveTo(to_x, to_y, duration=duration) pyautogui.mouseUp() def drag_file_to_app(self, file_path: str, target_x: int, target_y: int) -> None: """Drag a file to a specific position (e.g., drop file to app).""" self.drag_drop(0, 0, target_x, target_y, duration=1) ``` ```python def screenshot(self, region: Optional[Tuple[int, int, int, int]] = None, filename: Optional[str] = None): img = pyautogui.screenshot(region=region) if filename: img.save(filename) else: return img def screenshot_to(self, filename: str, region: Optional[Tuple[int, int, int, int]] = None) -> str: """Take a screenshot and save to filename. Returns filename.""" self.screenshot(region=region, filename=filename) return filename ``` ```python def copy_to_clipboard(self, text: str) -> None: try: import pyperclip pyperclip.copy(text) except Exception as e: logger.error(f"Clipboard copy error: {e}") def get_from_clipboard(self) -> Optional[str]: try: import pyperclip return pyperclip.paste() except Exception as e: logger.error(f"Clipboard paste error: {e}") return None ``` ### Technical Ana ...[truncated 2089 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Require `_check_approval()` before every public operation that reads or modifies desktop state, including screenshots, recording, clipboard access, scrolling, drag-and-drop, and window management. - Centralize authorization in a decorator or private execution wrapper so newly added methods cannot accidentally omit the check. - Use explicit descriptions such as `capture the entire screen`, `read clipboard contents`, or `record the screen for 30 seconds`. - Apply approval checks at the sensitive primitive layer rather than relying only on callers such as `run_steps()`. - Consider separate permissions for input control, screen capture, clipboard reads, clipboard writes, and recording. - Ensure denied operations return a clear failure result instead of silently appearing successful. - Add automated tests verifying that every sensitive API requests approval when `require_approval=True` and performs no action after denial. - Update examples to retain approval mode for privacy-sensitive operations unless the user has explicitly chosen otherwise. ]]>

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
__init__.py:72
Finding
Automatic X11 Display Selection Can Target an Unintended Desktop Session<![CDATA[ ## Vulnerability Details **File Location**: `__init__.py`, lines 72–100 **Vulnerability Type**: Unsafe graphical-session discovery **Risk Level**: Medium ### Vulnerable Code ```python def _check_environment(self) -> None: session = os.environ.get("XDG_SESSION_TYPE", "unknown") display = os.environ.get("DISPLAY") wayland = os.environ.get("WAYLAND_DISPLAY") if not display and not wayland: # Attempt auto-detect X11 DISPLAY via /tmp/.X11-unix detected = self._auto_detect_display() if detected: os.environ["DISPLAY"] = detected display = detected logger.info(f"Auto-detected DISPLAY={detected}") else: logger.warning("No DISPLAY/WAYLAND_DISPLAY detected. GUI automation may fail.") if session == "wayland": logger.warning( "Wayland session detected. Many distros restrict input control/screenshot; " "X11 is recommended for full functionality." ) def _auto_detect_display(self) -> Optional[str]: try: if os.path.isdir("/tmp/.X11-unix"): for name in sorted(os.listdir("/tmp/.X11-unix")): if name.startswith("X"): return f":{name[1:]}" except Exception: return None return None ``` ### Technical Analysis When neither `DISPLAY` nor `WAYLAND_DISPLAY` is configured, the code enumerates the globally visible `/tmp/.X11-unix` directory and selects the first socket whose name begins with `X`. It does not verify: - Which user owns the selected graphical session. - Whether the session belongs to the invoking process or user. - Whether appropriate Xauthority credentials are available. - Whether multiple displays exist and selection is ambiguous. - Whether the user approved connecting to the selected display. The selected value is written into `os.environ["DISPLAY"]`, after which PyAutoGUI, FFmpeg, `wmctrl`, and `xdotool` operations may target that session. X11 ...[truncated 1564 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Disable automatic display selection by default and require the caller to provide an explicit display. - If discovery is retained, present all candidate displays and require explicit user approval before selecting one. - Reject ambiguous environments containing multiple X11 sockets. - Validate the ownership and permissions of the X11 socket and confirm that the session belongs to the effective user. - Validate the corresponding Xauthority credentials before initializing desktop-control functionality. - Avoid modifying the global process environment; pass a validated environment only to the operation that requires it. - Record the selected display and validation result in security logs without exposing authentication material. - In service and container deployments, isolate X11 sockets and avoid mounting the host's complete `/tmp/.X11-unix` directory. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
__init__.py:692
Finding
Screen Recording Accepts Unrestricted FFmpeg Output Targets and Overwrites Existing Files<![CDATA[ ## Vulnerability Details **File Location**: `__init__.py`, lines 692–710 **Vulnerability Type**: Unvalidated external-tool output destination **Risk Level**: Medium ### Vulnerable Code ```python def record_screen(self, output_path: str, seconds: int = 30, fps: int = 25, display: Optional[str] = None, resolution: Optional[str] = None) -> str: """Record screen using ffmpeg. Requires ffmpeg installed.""" if display is None: display = os.environ.get("DISPLAY", ":0") if resolution is None: resolution = f"{self.screen_width}x{self.screen_height}" cmd = [ "ffmpeg", "-y", "-f", "x11grab", "-video_size", resolution, "-i", f"{display}.0", "-t", str(seconds), "-r", str(fps), output_path ] subprocess.run(cmd, check=True) return output_path ``` ### Technical Analysis The method passes `output_path` directly to FFmpeg and enables unconditional overwrite behavior through `-y`. Although the command correctly uses an argument array and is not vulnerable to shell metacharacter injection, FFmpeg performs its own interpretation of output targets. Depending on the installed FFmpeg build and supported protocols, an output value may identify either a local file or a protocol-based destination. The method does not restrict output to an approved directory, reject URI schemes, ensure that the destination is a regular file, or protect existing files from overwrite. It also does not request approval before capturing the display. The caller can additionally provide the display and resolution parameters without validation. Invalid values may cause failures or excessive resource consumption, although the principal security concern is the unrestricted destination for sensitive screen content. ### Attack Path 1. An attacker-controlled workflow or caller obtains access to `record_screen()`. 2. The caller supplies a writable existing path or an FFmpeg-supported o ...[truncated 1123 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Require explicit approval before starting every recording, including the display, duration, and destination in the prompt. - Resolve output paths to canonical filesystem paths and restrict them to a dedicated, user-approved recording directory. - Reject URI schemes, non-file destinations, option-like values, device files, named pipes, and other non-regular targets. - Remove unconditional `-y`; use non-overwriting behavior or require separate approval before replacing an existing file. - Check for symbolic links and perform safe file creation to reduce link-following and time-of-check/time-of-use risks. - Enforce upper bounds for duration, frame rate, and resolution to prevent resource exhaustion. - Validate the display string against an explicitly authorized display rather than accepting an arbitrary value. - Configure an FFmpeg protocol allowlist where supported so output is limited to local file handling. - Return a failure if output validation or authorization does not succeed. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (30)

Env Variable Harvesting

High
Category
Data Exfiltration
Content
Activate a window by title substring using wmctrl.
        """
        try:
            env = os.environ.copy()
            if not env.get("DISPLAY"):
                detected = self._auto_detect_display()
                if detected:
Confidence
60% confidence
Finding
Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Lp3

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding
The skill documents and demonstrates desktop automation features that imply shell execution, file access, and environment inspection, but it declares no explicit tool scope or permission boundaries. That mismatch increases the chance an agent can invoke powerful capabilities without clear user consent or policy enforcement, especially given functions like launching apps, recording screens, and writing files.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The screenshot example explicitly disables approval mode and writes screen contents to a file without any privacy warning. Screenshots can capture passwords, messages, tokens, internal documents, or unrelated user data, so normalizing no-approval capture makes accidental sensitive-data collection more likely.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The screen recording example records 30 seconds of desktop activity with approval disabled and no warning about capturing sensitive on-screen content. Recording is higher risk than a still screenshot because it can continuously collect credentials, chats, notifications, and workflow context over time.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The OCR example extracts text from whatever is visible on screen without warning that this may include secrets or personal data. OCR turns transient visual content into searchable text, which materially increases the risk of exposing passwords, tokens, emails, or confidential business information.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The flow recording and replay example captures and replays user interactions without warning about the sensitivity of recorded actions. Such recordings can encode credentials, personal messages, destructive UI sequences, or privileged administrative actions that could later be replayed unintentionally or abusively.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
import subprocess
        import re
        try:
            out = subprocess.check_output(['xrandr'], text=True)
            monitors = []
            for line in out.split('\n'):
                # Match: "HDMI-0 connected primary 1920x1080+1366+0"
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Description-Behavior Mismatch

Medium
Confidence
93% confidence
Finding
OCR-based text extraction significantly increases the ability to harvest sensitive data from the screen, including emails, tokens, messages, and other visible secrets. Because this capability is not clearly disclosed in the skill description, consumers may grant the skill broader trust than intended.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
import subprocess
        try:
            # Get window ID
            out = subprocess.check_output(['wmctrl', '-l'], text=True)
            for line in out.split('\n'):
                if title_substring.lower() in line.lower():
                    win_id = line.split()[0]
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
import subprocess
        try:
            # Get window ID
            out = subprocess.check_output(['wmctrl', '-l'], text=True)
            for line in out.split('\n'):
                if title_substring.lower() in line.lower():
                    win_id = line.split()[0]
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
import subprocess
        try:
            # Get window ID
            out = subprocess.check_output(['wmctrl', '-l'], text=True)
            for line in out.split('\n'):
                if title_substring.lower() in line.lower():
                    win_id = line.split()[0]
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
for line in out.split('\n'):
                if title_substring.lower() in line.lower():
                    win_id = line.split()[0]
                    subprocess.run(['wmctrl', '-ir', win_id, '-e', f'0,-1,-1,{width},{height}'])
                    return True
            return False
        except Exception as e:
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
for line in out.split('\n'):
                if title_substring.lower() in line.lower():
                    win_id = line.split()[0]
                    subprocess.run(['xdotool', 'windowminimize', win_id])
                    return True
            return False
        except Exception as e:
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
for line in out.split('\n'):
                if title_substring.lower() in line.lower():
                    win_id = line.split()[0]
                    subprocess.run(['wmctrl', '-ir', win_id, '-b', 'add,maximized_vert,maximized_horz'])
                    return True
            return False
        except Exception as e:
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
"""Detect current keyboard layout (returns: qwerty, qwertz, azerty, unknown)."""
        try:
            import subprocess
            out = subprocess.check_output(['setxkbmap', '-query'], text=True)
            layout = out.get('layout', '')
            if 'us' in layout:
                return 'qwerty'
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The screenshot and screenshot_to methods can capture and persist screen contents without any per-action approval or user-facing disclosure. Because screenshots often contain secrets or personal data, saving them directly to attacker-chosen paths increases the risk of covert data collection and retention.

Description-Behavior Mismatch

Medium
Confidence
94% confidence
Finding
The skill description frames the capability as mouse/keyboard/screenshot automation, but the code also records full-screen video sessions. That materially expands surveillance and data-capture scope, making it easier to collect sensitive on-screen information without users or integrators understanding the true capability surface.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
record_screen captures the full display to disk without any approval check, even though the class advertises approval mode as a safety feature. This enables silent collection of sensitive visual data, including messages, credentials, documents, and other user activity, and can create durable artifacts on disk for later exfiltration.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
"-r", str(fps),
            output_path
        ]
        subprocess.run(cmd, check=True)
        return output_path

    def get_pixel_color(self, x: int, y: int) -> Tuple[int, int, int]:
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Tainted flow: 'cmd' from os.environ.get (line 699, credential/environment) → subprocess.run (code execution)

Medium
Category
Data Flow
Content
"-r", str(fps),
            output_path
        ]
        subprocess.run(cmd, check=True)
        return output_path

    def get_pixel_color(self, x: int, y: int) -> Tuple[int, int, int]:
Confidence
65% confidence
Finding
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
"""
        try:
            import subprocess
            out = subprocess.check_output(["wmctrl", "-l"], text=True)
            titles = []
            for line in out.splitlines():
                parts = line.split(None, 3)
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
titles.append(title)
            return titles
        except FileNotFoundError:
            logger.error("wmctrl not found. Install: sudo apt-get install wmctrl")
            return []
        except Exception as e:
            logger.error(f"Error getting windows: {e}")
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
titles.append(title)
            return titles
        except FileNotFoundError:
            logger.error("wmctrl not found. Install: sudo apt-get install wmctrl")
            return []
        except Exception as e:
            logger.error(f"Error getting windows: {e}")
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
titles.append(title)
            return titles
        except FileNotFoundError:
            logger.error("wmctrl not found. Install: sudo apt-get install wmctrl")
            return []
        except Exception as e:
            logger.error(f"Error getting windows: {e}")
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
detected = self._auto_detect_display()
                if detected:
                    env["DISPLAY"] = detected
            subprocess.check_call(["wmctrl", "-a", title_substring], env=env)
            return True
        except FileNotFoundError:
            logger.error("wmctrl not found. Install: sudo apt-get install wmctrl")
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Static analysis

No suspicious patterns detected.