Back to skill

Security audit

MacOS Desktop Control

Security checks for vulnerabilities and agentic risk

Overview

This macOS desktop-control skill is mostly coherent, but it needs review because it combines screen, mouse, keyboard, clipboard, and AppleScript authority with real implementation flaws that can expose pasted text or allow AppleScript injection.

Review before installing. Use this only in a dedicated, low-sensitivity macOS session, avoid pasting secrets through it, clear or protect generated screenshots, and do not pass untrusted app names until the AppleScript interpolation and pasted-text logging issues are fixed.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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 (4)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/applescript_app.py:42
Finding
AppleScript Injection Through Unescaped Application Names<![CDATA[ ## Vulnerability Details **File Location**: `scripts/applescript_app.py:42-73`; related instances in `scripts/applescript_window.py:41-78` **Vulnerability Type**: AppleScript code injection **Risk Level**: High ### Vulnerable Code ```python if args.action == 'open': if args.path: subprocess.run(['open', args.path], check=True) emit(build_result('open', path=args.path, launch='open-path'), args.json_pretty) return if not args.app: raise SystemExit("Action 'open' requires --app or --path.") run_osascript([ f'tell application "{args.app}" to activate', ]) emit(build_result('open', app=args.app, launch='activate-app'), args.json_pretty) return if args.action == 'activate': if not args.app: raise SystemExit("Action 'activate' requires --app.") run_osascript([ f'tell application "{args.app}" to activate', 'tell application "System Events"', f'tell process "{args.app}" to set frontmost to true', 'end tell', ]) emit(build_result('activate', app=args.app, frontmost=True), args.json_pretty) return if args.action == 'is-running': if not args.app: raise SystemExit("Action 'is-running' requires --app.") out = run_osascript([ f'tell application "System Events" to return (name of processes) contains "{args.app}"', ]) emit(build_result('is-running', app=args.app, running=out.lower() == 'true'), args.json_pretty) return ``` The same unsafe construction appears in the window-inspection utility: ```python if args.action == 'title': out = run_osascript([ 'tell application "System Events"', f'tell process "{args.app}"', 'if (count of windows) > 0 then', 'return name of front window', 'else', 'return ""', 'end if', 'end tell', 'end tell', ]) ``` ### Technical Analysis The `--app` command-line value is inserted directly into Appl ...[truncated 1880 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Do not concatenate application names into AppleScript source. - Pass dynamic values as positional arguments to `osascript` and retrieve them through an `on run argv` handler. - Where possible, resolve applications using validated bundle identifiers rather than free-form names. - If source interpolation cannot be eliminated, implement a dedicated AppleScript string serializer that safely handles quotation marks, backslashes, control characters, and line breaks. - Optionally restrict input to an allowlist of installed application or process names. - Apply the same correction to every `args.app` interpolation in both `applescript_app.py` and `applescript_window.py`. - Add regression tests using names containing quotes, line breaks, and AppleScript keywords to verify that they remain data rather than executable syntax. A safer architectural pattern is: ```python script = ''' on run argv set appName to item 1 of argv tell application "System Events" return (name of processes) contains appName end tell end run ''' subprocess.run( ['osascript', '-e', script, args.app], check=True, capture_output=True, text=True, ) ``` ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/calibration.py:7
Finding
Symlink-Unsafe Writes to a Predictable Shared Temporary Directory<![CDATA[ ## Vulnerability Details **File Location**: `scripts/calibration.py:7-17` **Vulnerability Type**: Unsafe temporary-file handling and symbolic-link following **Risk Level**: Medium ### Vulnerable Code ```python DEFAULT_STATE_DIR = Path('/tmp/macos_desktop_control') DEFAULT_CALIBRATION_PATH = DEFAULT_STATE_DIR / 'calibration.json' def ensure_state_dir() -> None: DEFAULT_STATE_DIR.mkdir(parents=True, exist_ok=True) def save_calibration(data: Dict[str, Any], path: Path = DEFAULT_CALIBRATION_PATH) -> None: ensure_state_dir() path.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding='utf-8') ``` The default path is also exposed through the initialization command: ```python parser.add_argument('--output', type=Path, default=DEFAULT_CALIBRATION_PATH) args = parser.parse_args() data = detect_mapping() save_calibration(data, args.output) ``` ### Technical Analysis Calibration state is stored under the globally predictable path `/tmp/macos_desktop_control/calibration.json`. The directory is created with `exist_ok=True`, but the code does not verify that an existing directory is owned by the current user, has restrictive permissions, or is not otherwise attacker-controlled. `Path.write_text()` follows symbolic links and truncates the destination. No no-follow, exclusive-creation, ownership, regular-file, or atomic-replacement checks are performed. Consequently, another local user or process that can prepare the temporary path may redirect the calibration write to another file that the victim account is permitted to modify. The calibration loader also trusts an existing file without validating its ownership or structure, allowing attacker-controlled values to influence coordinate conversion and desktop actions. ### Attack Path 1. Before the victim uses the skill, a local attacker prepares the predictable `/tmp/macos_desktop_control` path under conditions that permit the victim process to access it. 2. The attacker places ...[truncated 947 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Store state in a user-private location such as `~/Library/Application Support/<application>` or `~/Library/Caches/<application>` rather than a shared `/tmp` pathname. - Create the state directory with mode `0700` and verify that it is owned by the current effective user. - Reject symbolic links and non-regular files for both the directory and calibration file. - Open new files using no-follow and exclusive-creation semantics where supported. - Write to a securely created temporary file in the same trusted directory, flush and synchronize it, and atomically replace the destination. - Set calibration files to mode `0600`. - Validate loaded JSON fields, numeric ranges, dimensions, and scale factors before using them. - If temporary storage remains necessary, create a per-user directory using a securely generated name rather than a fixed global path. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/keyboard.py:44
Finding
Sensitive Pasted Text Is Disclosed Through Standard Output<![CDATA[ ## Vulnerability Details **File Location**: `scripts/keyboard.py:44-54` **Vulnerability Type**: Plaintext sensitive-data exposure **Risk Level**: Medium ### Vulnerable Code ```python if args.action == 'paste': text = read_text_from_stdin() if args.stdin else args.text if text is None: raise SystemExit("Action 'paste' requires --text or --stdin.") copy_to_clipboard(text) maybe_sleep(args.paste_shortcut_delay) pyautogui.hotkey('command', 'v', interval=args.interval) print(json.dumps({'action': 'paste', 'text': text, 'length': len(text)})) return ``` ### Technical Analysis The paste operation emits the complete text value in its JSON result. Clipboard-based desktop automation can process passwords, API keys, authentication codes, private messages, personal data, or other confidential values. Standard output is frequently captured by agent transcripts, orchestration systems, CI logs, terminal logging, or monitoring infrastructure. Printing the value therefore creates a second plaintext copy outside the intended destination and expands the number of systems and users that may access it. The disclosure occurs for both `--text` and `--stdin` input. ### Attack Path 1. A user or higher-level workflow supplies a sensitive value to the `paste` action. 2. The script copies that value to the clipboard and sends Command+V. 3. The script serializes the same value in the `text` property of its standard-output JSON. 4. The calling agent, task runner, logging service, or terminal capture records the output. 5. Anyone with access to those records can recover the pasted secret even after the clipboard or destination field is cleared. ### Impact Assessment The impact is disclosure of any confidential content processed by the paste function. Exposure is limited to parties that can read captured stdout or downstream logs, but those records may have broader access and longer retention than the target application. This issue doe ...[truncated 139 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Remove the pasted value from standard output. - Return only non-sensitive metadata, for example: ```python print(json.dumps({ 'action': 'paste', 'length': len(text), 'completed': True, })) ``` - Avoid logging clipboard content in exception messages, debug output, telemetry, or agent-visible results. - If debugging requires content visibility, make it an explicit opt-in mode and redact values by default. - Document that command-line arguments may be visible to local process-inspection tools; prefer stdin for sensitive values. - Consider restoring or clearing the clipboard after pasting when workflow compatibility permits, while noting that clipboard clearing alone does not address stdout disclosure. ]]>

T08 · Insecure Dependencies

Warning
Location
requirements.txt:1
Finding
Unpinned and Unhashed Third-Party Dependencies<![CDATA[ ## Vulnerability Details **File Location**: `requirements.txt:1-5`; installation guidance in `SKILL.md:112-116` **Vulnerability Type**: Non-reproducible dependency resolution and supply-chain exposure **Risk Level**: Medium ### Vulnerable Code ```text pyautogui>=0.9.54 Pillow>=10.0.0 opencv-python>=4.8.0 pyobjc-framework-Vision>=10.0 pyobjc-framework-Quartz>=10.0 ``` The documented installation command is: ```bash pip install -r requirements.txt ``` ### Technical Analysis Every dependency uses an open-ended minimum-version constraint. A new installation may therefore retrieve any future release accepted by the resolver rather than a specifically reviewed version. No lock file or package hashes are provided to verify artifact integrity. The reviewed package names do not, by themselves, establish dependency confusion or typosquatting. The confirmed weakness is that the effective installed code can change over time without a corresponding change to this project, reducing reproducibility and increasing exposure to compromised upstream releases, malicious distribution artifacts, or incompatible updates. Python package installation can execute build-backend or installation-related code, so dependency integrity is especially important for a desktop-control skill with Screen Recording and Accessibility permissions. ### Attack Path 1. A user follows the documented `pip install -r requirements.txt` command. 2. The package resolver queries the configured Python package index. 3. It selects the newest available versions satisfying the open-ended lower bounds. 4. Those versions may differ from the releases originally tested or reviewed. 5. If an upstream account, release, package index, or build artifact is compromised, malicious package code can execute during installation or when imported by the skill. 6. That code runs with the privileges of the installing or executing user. ### Impact Assessment A compromised dependency could execute arbitrary cod ...[truncated 349 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Pin every direct dependency to an exact, reviewed version using `==`. - Generate and commit a lock file that includes resolved transitive dependencies. - Require cryptographic hashes for downloaded distributions, for example through a hash-locked requirements file and `pip --require-hashes`. - Use a trusted, explicitly configured package index and disable unintended extra indexes. - Prefer reviewed binary wheels where appropriate and avoid unexpected source builds. - Add automated dependency vulnerability and provenance scanning. - Establish a controlled update process that reviews release notes, verifies artifacts, runs tests, and updates pins deliberately. - Install dependencies in an isolated virtual environment with the minimum required privileges. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (24)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The description claims a broad generic macOS desktop control tool including app and window semantics as well as screenshot, OCR, mouse, and keyboard workflows. The supplied code only implements four app-level actions: open an app or app bundle path, activate/make an app frontmost, test whether an app is running, and query the frontmost app. It does not implement screenshots, OCR, mouse input, keyboard input, or meaningful window control. This is therefore a description-behavior mismatch due to substantial overstatement of capabilities, even though the AppleScript/macOS app-control portion is partially accurate.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
The declared description presents a broad macOS desktop control skill with multiple interaction modalities (AppleScript app/window semantics, screenshots, OCR, mouse, keyboard workflows). The supplied code only implements read-only window inspection for a named application process: retrieving the front window title, window count, or all window titles. It does not perform screenshots, OCR, mouse input, keyboard input, or broader desktop control actions. While the AppleScript/window-semantics portion is related, the actual code is materially narrower than the declared purpose, so the description does not accurately represent this code chunk.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The declared description describes a broad macOS desktop automation skill involving AppleScript, screenshots, OCR, and input control. The supplied code only implements image cropping on an existing image file using PIL. This is a materially different primary purpose and lacks the core declared behaviors entirely. Therefore the description does not accurately represent the code chunk.

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill documents shell execution, file reads/writes, screenshot capture, and desktop automation, but it does not declare any explicit tool scope such as allowed-tools or permissions. That omission weakens containment and review because an agent may invoke broad local capabilities without a clear least-privilege contract, which is especially risky for a desktop-control skill with access to screen contents and input synthesis.

Description-Behavior Mismatch

Medium
Confidence
88% confidence
Finding
The skill description and feature list present AI semantic understanding as a core built-in visual targeting method, yet the documented scripts list includes `locate_text_ocr.py` and `locate_image_opencv.py` without any corresponding AI semantic locator implementation. This overstates what the skill itself appears to actually provide, making the claimed behavior broader than the documented code surface.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The skill encourages screenshot capture and OCR over arbitrary desktop content but does not explicitly warn that these operations may collect passwords, messages, tokens, financial data, or other sensitive on-screen information. In a desktop-control context, that omission is meaningful because operators may use the skill in privileged sessions and underestimate the privacy and exfiltration risk of captured images and OCR output.

Intent-Code Divergence

Medium
Confidence
97% confidence
Finding
The heading and surrounding text say AI semantic understanding is the default first-choice locator, but the example command under that heading invokes `locate_text_ocr.py` with a text string. This actively contradicts the documented distinction between AI semantic understanding and OCR fallback.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill defaults to clipboard-based paste for all text input but does not clearly warn that this can overwrite the user's clipboard and may paste secrets into the wrong field if focus is incorrect. In a desktop automation skill that can click and paste into arbitrary apps, this can cause unintended disclosure of sensitive text or destructive actions in the wrong context.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def run_osascript(lines: List[str]) -> str:
    proc = subprocess.run(
        ["osascript", *sum([["-e", line] for line in lines], [])],
        check=True,
        capture_output=True,
Confidence
97% confidence
Finding
The code builds AppleScript source by directly interpolating the untrusted --app argument into script lines passed to osascript. An attacker can inject quotes and additional AppleScript statements, leading to arbitrary AppleScript execution, which in this macOS desktop-control context can drive apps, read UI state, trigger clicks/keystrokes, and potentially access sensitive user data via automation permissions.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
if args.action == 'open':
        if args.path:
            subprocess.run(['open', args.path], check=True)
            emit(build_result('open', path=args.path, launch='open-path'), args.json_pretty)
            return
        if not args.app:
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
def run_osascript(lines: List[str]) -> str:
    proc = subprocess.run(
        ["osascript", *sum([["-e", line] for line in lines], [])],
        check=True,
        capture_output=True,
Confidence
95% confidence
Finding
The code builds AppleScript source using an f-string with untrusted input (`args.app`) and passes it directly to `osascript`. Although `subprocess.run` is invoked without a shell, this is still an AppleScript injection issue: a crafted application name containing quotes and script fragments can break out of the string literal and execute arbitrary AppleScript commands, enabling UI automation, data access, or other desktop actions under the user's permissions.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The script captures the entire macOS screen and writes it to a predictable file path under /tmp without any built-in notice, consent prompt, or disclosure mechanism. In a desktop-control skill, screenshots can contain sensitive data such as credentials, messages, or documents, so silent persistence of captured images increases privacy and data-exposure risk even if the feature is intended for automation.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The script can write arbitrary text to the macOS clipboard and immediately simulate Command+V without any confirmation, warning, or target validation. In a desktop-control skill, this can cause unintended data entry into whatever window has focus, including terminals, password fields, chats, or admin consoles, increasing the risk of secret leakage or destructive command execution.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def copy_to_clipboard(text: str) -> None:
    subprocess.run(['pbcopy'], input=text, text=True, check=True)


def main() -> None:
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Natural-Language Policy Violations

Low
Confidence
84% confidence
Finding
The introductory text is in Chinese and explicitly reassures Chinese users, which creates a locale-specific framing without any corresponding statement that other languages/locales are equally supported or user-selectable. Under the policy, language or locale constraints should be optional, documented as justified, or offer user choice.

Intent-Code Divergence

Low
Confidence
92% confidence
Finding
The feature list states the skill can "Type text" as part of keyboard control, while later guidance says text entry should not use simulated typing and instead always use clipboard paste. Those statements describe different intended behaviors for how text input is performed.

Unpinned Dependencies

Low
Category
Supply Chain
Content
pyautogui>=0.9.54
Pillow>=10.0.0
opencv-python>=4.8.0
pyobjc-framework-Vision>=10.0
Confidence
95% confidence
Finding
The dependency is specified with a lower bound only, which allows future unreviewed versions to be installed and makes builds non-reproducible. In a desktop-control skill with GUI automation privileges, a compromised or breaking upstream release could introduce supply-chain risk or unsafe behavior.

Unpinned Dependencies

Low
Category
Supply Chain
Content
pyautogui>=0.9.54
Pillow>=10.0.0
opencv-python>=4.8.0
pyobjc-framework-Vision>=10.0
pyobjc-framework-Quartz>=10.0
Confidence
98% confidence
Finding
Pillow is unpinned and has a history of security advisories, so using only a minimum version leaves the installed version uncertain and non-reproducible. Because this skill processes screenshots/images, image-parsing dependencies are directly in the execution path, increasing exposure if a vulnerable release is resolved.

Unverifiable Dependency: Pillow has 16 known advisory(ies) (CVE-2016-2533 (Pillow buffer overflow in ImagingPcdDecode); CVE-2023-50447 (Arbitrary Code Execution in Pillow); CVE-2021-27922 (Pillow Uncontrolled Resource Consumption) +13 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
97% confidence
Finding
Pillow has multiple known advisories, and because the requirement is not pinned, there is no assurance that installs will avoid affected versions. This is especially relevant here because the skill handles screenshots and OCR inputs, so vulnerable image-processing code would likely be exercised.

Unpinned Dependencies

Low
Category
Supply Chain
Content
pyautogui>=0.9.54
Pillow>=10.0.0
opencv-python>=4.8.0
pyobjc-framework-Vision>=10.0
pyobjc-framework-Quartz>=10.0
Confidence
98% confidence
Finding
opencv-python is unpinned, so dependency resolution may select different versions over time, including releases with known flaws or regressions. Since the skill uses OCR/computer-vision workflows, OpenCV is a core attack surface and this context makes version uncertainty more relevant than in a package that never handles images.

Unverifiable Dependency: opencv-python has 16 known advisory(ies) (CVE-2017-12864 (Integer Overflow or Wraparound in OpenCV); CVE-2017-12598 (Out-of-bounds Read in OpenCV ); CVE-2019-14493 (NULL Pointer Dereference in OpenCV.) +13 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
97% confidence
Finding
opencv-python has known security advisories, and the unpinned requirement means a vulnerable version could be installed without visibility. Because OpenCV is central to this skill’s screenshot/OCR and vision pipeline, any parser or memory-safety issue in the library is more likely to be reachable.

Unpinned Dependencies

Low
Category
Supply Chain
Content
pyautogui>=0.9.54
Pillow>=10.0.0
opencv-python>=4.8.0
pyobjc-framework-Vision>=10.0
pyobjc-framework-Quartz>=10.0
Confidence
90% confidence
Finding
The Vision framework binding is not pinned to an exact version, which weakens reproducibility and allows unreviewed upstream changes into builds. While this is usually lower risk than native parsing libraries with many CVEs, it still creates supply-chain uncertainty in a privileged automation skill.

Unpinned Dependencies

Low
Category
Supply Chain
Content
Pillow>=10.0.0
opencv-python>=4.8.0
pyobjc-framework-Vision>=10.0
pyobjc-framework-Quartz>=10.0
Confidence
90% confidence
Finding
The Quartz framework binding is also specified as a minimum version only, allowing future dependency drift. For a macOS desktop-control skill interacting with display/input APIs, dependency integrity matters because these libraries operate in a sensitive, high-privilege workflow.

Natural-Language Policy Violations

Low
Confidence
92% confidence
Finding
The code hard-codes recognition languages to Simplified Chinese, Traditional Chinese, and US English. Under the natural-language policy rule, forcing specific languages without user opt-in or a documented region-specific justification is a locale policy concern.

Static analysis

No suspicious patterns detected.