Back to skill

Security audit

mac-use

Security checks for vulnerabilities and agentic risk

Overview

This skill appears to do what it says, but it gives an agent broad Mac screen-reading and click/type control with weak scoping, risky shared temp files, and non-isolated install guidance.

Install only if you are comfortable giving the host process Screen Recording and Accessibility control over your Mac. Use it in a virtual environment, avoid system-wide pip installation, keep sensitive windows and clipboard contents clear, do not use it for passwords or secrets, and require explicit approval before any send, purchase, delete, settings, account, or financial action.

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 (2)

T08 · Insecure Dependencies

Warning
Location
requirements.txt:1
Finding
Unpinned dependencies and unsafe system-wide installation guidance<![CDATA[ ## Vulnerability Details **File Location**: `requirements.txt:1-3`; installation guidance in `SKILL.md:19-21` and `README.md:73-75` **Vulnerability Type**: Unbounded dependency resolution and system environment modification **Risk Level**: Medium ### Vulnerable Code `requirements.txt:1-3`: ```text pyobjc-framework-Vision>=12.0 pyautogui>=0.9 Pillow>=10.0 ``` `SKILL.md:19-21`: ```bash pip3 install --break-system-packages -r {baseDir}/requirements.txt ``` `README.md:73-75`: ```bash pip3 install --break-system-packages -r requirements.txt ``` ### Technical Analysis All three third-party dependencies use open-ended lower-bound constraints. Consequently, an installation can retrieve package versions that did not exist when the Skill was reviewed. The project provides neither a lock file nor cryptographic hashes that bind installation to reviewed artifacts. The primary installation guidance also uses `--break-system-packages`, which bypasses Python's externally managed environment protection. This can modify a shared Homebrew or system-level Python environment instead of isolating the Skill's dependencies. Python packages may execute build-backend or setup-related code during installation, and their modules execute code when imported. The Skill imports these dependencies immediately in `scripts/mac_use.py`, including `pyautogui`, `Vision`, `Quartz`, and Pillow modules. A compromised or malicious future package release could therefore execute with the privileges of the user running the installation or Skill. This finding does not establish that the currently named packages are malicious. The vulnerability is the absence of version and artifact integrity controls combined with guidance to alter a shared Python environment. ### Attack Path 1. An attacker compromises the release process or distribution account of one of the declared dependencies, or publishes a future release containing malicious installation or import behavior. 2. A user follows ...[truncated 1284 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin every direct dependency to an exact, reviewed version rather than using open-ended `>=` constraints. 2. Generate and commit a lock file that includes transitive dependencies. 3. Require cryptographic hashes for downloaded artifacts, for example through a hash-locked requirements file and `pip install --require-hashes`. 4. Review dependency updates before changing the lock file, including package ownership, release history, source distribution behavior, and published wheels. 5. Make an isolated virtual environment the default installation procedure: ```bash python3 -m venv .venv source .venv/bin/activate python -m pip install --require-hashes -r requirements.lock ``` 6. Remove `--break-system-packages` from the recommended setup. If retained as a non-default alternative, clearly warn that it modifies a shared Python environment. 7. Consider automated dependency vulnerability scanning and reproducible builds as part of release validation. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/mac_use.py:35
Finding
Predictable shared temporary files permit state tampering and file clobbering<![CDATA[ ## Vulnerability Details **File Location**: `scripts/mac_use.py:35-37`, `scripts/mac_use.py:411-422`, and `scripts/mac_use.py:442-444` **Vulnerability Type**: Predictable temporary files, symlink following, and untrusted state reuse **Risk Level**: Medium ### Vulnerable Code `scripts/mac_use.py:35-37` defines fixed paths in a shared temporary directory: ```python SCREENSHOT_PATH = "/tmp/mac_use.png" SCREENSHOT_FULL = "/tmp/mac_use_full.png" ELEMENTS_FILE = "/tmp/mac_use_elements.json" ``` `scripts/mac_use.py:411-422` writes trusted click state using an ordinary, truncating file open: ```python # Save element map for clicknum with open(ELEMENTS_FILE, "w") as f: json.dump({ "window": { "id": win["id"], "app": win["app"], "title": win["title"], "x": win["x"], "y": win["y"], "w": win["w"], "h": win["h"], }, "scale": scale, "elements": elements, }, f, ensure_ascii=False, indent=2) ``` `scripts/mac_use.py:442-444` later trusts the same shared file: ```python try: with open(ELEMENTS_FILE) as f: data = json.load(f) except FileNotFoundError: fail({"error": "no_elements", "message": "Run screenshot first."}) ``` The screenshot files are likewise written through predictable names, including: ```python canvas.save(SCREENSHOT_PATH) ``` ### Technical Analysis The implementation stores screenshots and action-authorizing element state at fixed, globally predictable paths under `/tmp`. It does not: - Create a private per-session temporary directory. - Use exclusive file creation. - Reject symbolic links. - Validate file ownership or file type before reading. - Apply explicit restrictive permissions. - Authenticate the state with a session-specific nonce. - Prevent concurrent Skill invocations from overwriting one another. The JSON file is security-sensitive because `clicknum` loads its window metadata, scale, element numbers, and canvas coor ...[truncated 3586 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Create a private temporary directory for each process or session: ```python import tempfile SESSION_DIR = tempfile.mkdtemp(prefix="mac_use-") SCREENSHOT_PATH = os.path.join(SESSION_DIR, "screenshot.png") SCREENSHOT_FULL = os.path.join(SESSION_DIR, "full.png") ELEMENTS_FILE = os.path.join(SESSION_DIR, "elements.json") ``` 2. Ensure the directory has mode `0700` and each sensitive file has mode `0600`. 3. Use exclusive creation semantics such as `os.open` with `O_CREAT | O_EXCL | O_NOFOLLOW` where supported. 4. Before reading state, validate that the path: - Is a regular file rather than a symbolic link. - Is owned by the expected effective user. - Has restrictive permissions. - Belongs to the current session. 5. Include an unpredictable session identifier in the state path and require `clicknum` to reference that session explicitly. 6. Bind the element map to the captured window and screenshot using a cryptographic digest or authenticated session state. 7. Revalidate the current window geometry and identity immediately before clicking. 8. Use atomic writes through a securely created temporary file followed by a same-directory rename. 9. Remove screenshots and element maps when the session ends, while allowing an explicit opt-in debugging mode if retention is required. 10. Prevent accidental cross-session reuse by avoiding process-global fixed filenames and by rejecting stale state. ]]>
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
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (14)

Lp3

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding
The skill clearly instructs use of shell commands and writes artifacts to /tmp, but it declares no explicit tool scope or permission boundaries. In an agent environment, that mismatch increases the chance the skill can be invoked with broader-than-expected capabilities, enabling unintended command execution or filesystem side effects.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
This skill is designed to control arbitrary macOS GUI applications by opening apps, clicking, typing, and pressing keys, which can directly trigger real-world actions like sending messages, approving dialogs, changing settings, or modifying/deleting data. While it includes some operational guidance, it does not prominently frame these actions as potentially destructive or require confirmation before high-risk interactions, making accidental harmful automation more likely.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def activate_app(app_name):
    """Bring an app to the foreground."""
    subprocess.run(
        ["osascript", "-e", f'tell application "{app_name}" to activate'],
        capture_output=True,
        timeout=5,
Confidence
91% confidence
Finding
This builds AppleScript code by interpolating the untrusted app_name directly into an osascript -e string. A crafted app name containing quotes or AppleScript syntax could break out of the intended string literal and execute arbitrary AppleScript commands, which is especially dangerous in a GUI automation skill that already has permission to control applications.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
f'to perform action "AXRaise" of window "{safe_title}"'
    )
    try:
        subprocess.run(["osascript", "-e", script], capture_output=True, timeout=5)
        time.sleep(0.3)
    except Exception:
        pass
Confidence
93% confidence
Finding
Although the window title is partially escaped, app_name is still interpolated directly into AppleScript source in the System Events tell process clause. This enables AppleScript injection via crafted process names and could be used to trigger unintended automation actions on the host.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The skill captures a full-screen screenshot, crops it, and persists image data to predictable files under /tmp without any user-facing warning or minimization. In the context of a desktop-control skill, this can expose sensitive information from emails, chats, passwords, or unrelated windows if the capture or storage is accessed by other local processes or reused unexpectedly.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def capture_full_screen():
    """Take a full-screen screenshot. Returns True on success."""
    r = subprocess.run(
        ["/usr/sbin/screencapture", "-x", SCREENSHOT_FULL],
        capture_output=True, timeout=10,
    )
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
else:
        mod_str = ""
    script = f'tell application "System Events" to keystroke "{key}"{mod_str}'
    r = subprocess.run(["osascript", "-e", script], capture_output=True, timeout=10)
    if r.returncode != 0:
        sys.stderr.write(f"osascript keystroke failed: {r.stderr.decode().strip()}\n")
Confidence
95% confidence
Finding
The keystroke AppleScript is created by interpolating the untrusted key value into a quoted script string without escaping. An attacker who can influence combo/main_key could inject additional AppleScript statements, causing arbitrary GUI actions or broader system automation under the user's privileges.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
else:
        mod_str = ""
    script = f'tell application "System Events" to key code {code}{mod_str}'
    r = subprocess.run(["osascript", "-e", script], capture_output=True, timeout=10)
    if r.returncode != 0:
        sys.stderr.write(f"osascript keycode failed: {r.stderr.decode().strip()}\n")
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The type command writes arbitrary text to the system clipboard via pbcopy and then pastes it, overwriting the user's existing clipboard contents without notice or restoration. In a GUI automation skill, that can leak or destroy sensitive clipboard data and can also expose the injected text to clipboard history tools or other local observers.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
if args.app:
        activate_and_find(args.app)

    proc = subprocess.Popen(["pbcopy"], stdin=subprocess.PIPE)
    proc.communicate(args.text.encode("utf-8"))

    keystroke_via_osascript("v", ["command"])
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Unpinned Dependencies

Low
Category
Supply Chain
Content
pyobjc-framework-Vision>=12.0
pyautogui>=0.9
Pillow>=10.0
Confidence
97% confidence
Finding
The dependency is specified with a lower bound only, which allows installation of any newer release and makes builds non-reproducible. This increases supply-chain risk because future incompatible or compromised versions could be pulled in without review, though the issue is configuration weakness rather than direct exploit code.

Unpinned Dependencies

Low
Category
Supply Chain
Content
pyobjc-framework-Vision>=12.0
pyautogui>=0.9
Pillow>=10.0
Confidence
97% confidence
Finding
Using an unpinned pyautogui version means the installed package may vary over time, undermining reproducibility and allowing unexpected vulnerable or malicious upstream releases to be introduced. In a GUI-control skill, this matters because the package has direct input automation capability and would run with the user's desktop permissions.

Unpinned Dependencies

Low
Category
Supply Chain
Content
pyobjc-framework-Vision>=12.0
pyautogui>=0.9
Pillow>=10.0
Confidence
98% confidence
Finding
Pillow is unpinned, so deployments may resolve to different versions, including releases with known security issues. Because this skill processes screenshots/images as part of macOS GUI automation, an image library weakness is more relevant than in unrelated contexts and could expose the host to parsing-related vulnerabilities or denial of service.

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
95% confidence
Finding
Pillow has multiple known advisories, and because no exact version is pinned, there is no way to verify whether the installed release includes fixes. Given the skill's visual automation purpose, Pillow is likely used on attacker-influenced image data such as screenshots or loaded assets, which makes known image-parsing flaws more operationally relevant.

Static analysis

No suspicious patterns detected.