Back to skill

Security audit

wecom-gui-message

Security checks for vulnerabilities and agentic risk

Overview

This skill is a real WeCom message automation tool, but it has unsafe command execution and message-sending safeguards that users should review before installing.

Install only if you are comfortable granting desktop control and screen-capture capabilities to a script that can send live WeCom messages. Review or fix the shell command construction, require recipient confirmation before sending, avoid full-screen capture, and clean up temporary screenshots before using it with sensitive chats or work 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 (4)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/send_message.py:48
Finding
Arbitrary Shell Command Injection Through Recipient and Message Inputs<![CDATA[ ## Vulnerability Details **File Location**: `scripts/send_message.py`, lines 48–52, 140–145, and 202–205 **Vulnerability Type**: OS command injection caused by shell command construction **Risk Level**: High ### Vulnerable Code ```python def run(cmd, timeout=15): log(f"→ {cmd[:120]}") result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=timeout) if result.returncode != 0 and result.stderr: log(f" stderr: {result.stderr[:200]}") return result ``` ```python def ocr(image_path, keyword=None): """Swift Vision OCR""" ocr_script = os.path.join(SCRIPT_DIR, "ocr_screen.swift") cmd = f'swift {ocr_script} "{image_path}"' if keyword: cmd += f' "{keyword}"' result = run(cmd, timeout=30) ``` ```python def paste_text(text): safe_text = text.replace('"', '\\"') run(f'peekaboo paste --text "{safe_text}" --app "{WECOM_BUNDLE_ID}"') time.sleep(1) ``` ### Technical Analysis The central `run()` function invokes commands with `shell=True`. Both the recipient name and message text originate from command-line arguments and are eventually interpolated into shell command strings. Escaping only the double-quote character is not sufficient for values inserted into a double-quoted shell argument. Shell constructs such as command substitution remain active inside double quotes. For example, `$(command)` and backtick-based substitutions can still be evaluated by the shell. The recipient name reaches `ocr()` as the `keyword` argument through `ocr_find()`. The message reaches `paste_text()`. Consequently, both inputs can introduce shell syntax before the intended `swift` or `peekaboo` process is executed. This is an exploitable command injection flaw rather than merely an argument-injection issue because `/bin/sh` interprets the constructed command. ### Attack Path 1. An attacker causes the Skill to be invoked with a crafted recipient name or message. 2. The crafted value contains ...[truncated 985 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Remove `shell=True` and pass every executable and argument as a separate list element: ```python def run(args, timeout=15): result = subprocess.run( args, shell=False, capture_output=True, text=True, timeout=timeout, check=False, ) return result ``` Invoke the affected tools without constructing shell strings: ```python args = ["swift", ocr_script, image_path] if keyword: args.append(keyword) result = run(args, timeout=30) ``` ```python run([ "peekaboo", "paste", "--text", text, "--app", WECOM_BUNDLE_ID, ]) ``` Apply this argument-array approach consistently to `swift`, `peekaboo`, `cliclick`, `screencapture`, `open`, and `osascript` where practical. Validate recipient length and reject control characters as defense in depth, but do not rely on input filtering or shell escaping as the primary fix. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/send_message.py:331
Finding
Ambiguous OCR Matching Can Send Messages to the Wrong Recipient<![CDATA[ ## Vulnerability Details **File Location**: `scripts/send_message.py`, lines 331–366 and 401–438 **Vulnerability Type**: Fail-open recipient selection and insufficient identity verification **Risk Level**: High ### Vulnerable Code ```python for attempt in range(MAX_RETRIES): img, win = screenshot(f"msglist_{attempt}.png") # 精确匹配 target = ocr_find(img, name) # 回退到前两个字 if not target and len(name) >= 2: target = ocr_find(img, name[:2]) if target: log(f" ✓ 找到 '{target['text']}'") click_ocr_target(win, target) time.sleep(2) # 验证:右侧聊天区是否有内容 img2, _ = screenshot("after_click.png") ocr_result = ocr(img2) if ocr_result and ocr_result.get("success"): right_texts = [t for t in ocr_result.get("all_texts", []) if t["center_x"] > 800] if len(right_texts) > 3: log(f" ✓ 聊天窗口已打开(右侧 {len(right_texts)} 元素)") return True ``` ```python # 发送 log("按回车发送...") press_key("return") time.sleep(2) # 验证发送 img, _ = screenshot("sent_verify.png") target = ocr_find(img, check_text) if target: log(" ✅ 消息已在聊天记录中确认") else: log(" ⚠ 未在聊天记录确认(回车已按,大概率已发送)") return True ``` ### Technical Analysis The OCR implementation uses substring matching, and recipient selection falls back to the first two characters of the requested name. Two-character prefixes are commonly non-unique. If several contacts or groups share the same prefix, the first OCR result can be selected without determining whether it represents the intended recipient. After clicking the result, the code only verifies that more than three OCR elements appear in the right side of the window. This confirms that some chat pane is open, but it does not confirm the selected conversation's identity. The sending routine then presses Return and reports success even when post-send OCR cannot confirm the message. These fail-open behaviors combine to cr ...[truncated 1069 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Require an exact, unique recipient match and fail closed: 1. Normalize OCR and requested names using a defined Unicode and whitespace normalization policy. 2. Match the entire visible conversation name rather than using substring matching. 3. Remove the `name[:2]` fallback. 4. If zero or multiple exact matches exist, abort and require explicit user confirmation. 5. After clicking a conversation, OCR a constrained header region and compare its complete name with the requested recipient. 6. Do not treat generic right-pane content as proof of recipient identity. 7. Before pressing Return, perform a final recipient-header check. 8. Return failure when post-send verification cannot establish that the message appeared in the intended conversation. Where WeCom exposes stable accessibility identifiers, use those identifiers instead of OCR and visual position matching. ]]>

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
scripts/send_message.py:121
Finding
Full-Screen Capture Collects Data Outside the WeCom Task Scope<![CDATA[ ## Vulnerability Details **File Location**: `scripts/send_message.py`, lines 121–134 and 263–271 **Vulnerability Type**: Overbroad screen-data collection **Risk Level**: Medium ### Vulnerable Code ```python def screenshot(filename="capture.png"): """截取企微窗口(非全屏!避免 OCR 空结果) 返回 (image_path, window_info) window_info 用于坐标换算: screen_x = win_x + ocr_logic_x """ os.makedirs(TMP_DIR, exist_ok=True) path = os.path.join(TMP_DIR, filename) win = get_window_info() if win: run(f"screencapture -x -l {win['id']} {path}") else: log(" ⚠ 无法获取窗口ID,回退到全屏截图") run(f"screencapture -x {path}") # 全屏截图时坐标就是屏幕坐标,offset 为 0 win = {"id": 0, "x": 0, "y": 0, "width": 1512, "height": 982} return path, win ``` ```python for i in range(max_wait // 3): # 用全屏截图检测通知(通知在屏幕右上角,不在企微窗口内) notif_path = os.path.join(TMP_DIR, "notif_check.png") run(f"screencapture -x {notif_path}") has_notif, matched = ocr_has_keyword(notif_path, NOTIFICATION_KEYWORDS) ``` ### Technical Analysis The stated task is to automate messaging within the WeCom desktop application. Window-scoped captures are sufficient for most of that purpose. However, the code captures the entire display while checking for notifications and falls back to full-screen capture whenever WeCom window discovery fails. A full-screen image can include unrelated applications, credentials, private documents, browser content, notifications, and other data outside the WeCom automation scope. The captures are subsequently processed by OCR and retained as files. The behavior relies on Screen Recording permission, which is highly sensitive. Using that permission to collect unrelated screen content violates least-privilege and data-minimization principles. ### Attack Path 1. The Skill runs while unrelated sensitive content is visible on the display. 2. The notification-check routine takes a full-screen screenshot, or window lookup fails and tri ...[truncated 658 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Remove the full-screen fallback and fail closed if the WeCom window cannot be identified: ```python if not win: raise RuntimeError("Unable to identify the WeCom window safely") ``` Replace full-screen notification detection with one of the following: - Window-scoped WeCom screenshots. - Accessibility APIs that report modal windows or focus state without capturing screen pixels. - A tightly bounded capture of only the minimum required notification region, if this is essential and clearly disclosed. - A user confirmation step when external system UI obstructs the workflow. Minimize Screen Recording usage, document exactly what regions are captured, and avoid retaining images after each processing step. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/send_message.py:27
Finding
Sensitive Screenshots Are Retained in a Predictable Shared Temporary Directory<![CDATA[ ## Vulnerability Details **File Location**: `scripts/send_message.py`, lines 27, 121–134, 232–245, and 452 **Vulnerability Type**: Unsafe temporary-file handling and sensitive-data retention **Risk Level**: Medium ### Vulnerable Code ```python TMP_DIR = "/tmp/wecom-gui" ``` ```python def screenshot(filename="capture.png"): """截取企微窗口(非全屏!避免 OCR 空结果) 返回 (image_path, window_info) window_info 用于坐标换算: screen_x = win_x + ocr_logic_x """ os.makedirs(TMP_DIR, exist_ok=True) path = os.path.join(TMP_DIR, filename) win = get_window_info() if win: run(f"screencapture -x -l {win['id']} {path}") else: log(" ⚠ 无法获取窗口ID,回退到全屏截图") run(f"screencapture -x {path}") win = {"id": 0, "x": 0, "y": 0, "width": 1512, "height": 982} return path, win ``` ```python def capture_qr_code(): log("截取二维码...") for retry in range(5): time.sleep(3) qr_path, _ = screenshot(f"qr_{retry}.png") has_qr, _ = ocr_has_keyword(qr_path, QR_KEYWORDS) if has_qr: log(f" ✓ 二维码已确认(第 {retry+1} 次)") return qr_path return qr_path ``` ```python os.makedirs(TMP_DIR, exist_ok=True) ``` ### Technical Analysis The code stores screenshots in the fixed path `/tmp/wecom-gui` using predictable file names such as `login_check.png`, `qr_0.png`, `pre_send.png`, and `sent_verify.png`. The directory is created without an explicit restrictive mode, without verifying that it is owned by the current user, and without checking whether it or its contents are symbolic links. The implementation also has no cleanup routine. These files can contain login QR imagery, conversation content, recipient names, draft messages, sent messages, and full-screen content. Because filenames are reused between executions, old data can persist and new runs can overwrite attacker-prepared paths. The exact feasibility of cross-user access depends on the host's directory ownership and permission ...[truncated 973 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Use a unique, private temporary directory for every invocation: ```python import tempfile with tempfile.TemporaryDirectory(prefix="wecom-gui-") as tmp_dir: # Store and process screenshots only inside tmp_dir. ... ``` Apply the following safeguards: 1. Ensure the temporary directory is owned by the current user and has mode `0700`. 2. Create files securely with randomized names and restrictive permissions. 3. Reject symbolic links and unexpected pre-existing paths. 4. Delete each screenshot immediately after OCR where possible. 5. Use a `try`/`finally` block to guarantee cleanup after exceptions and timeouts. 6. Avoid retaining QR-code, pre-send, sent-message, and full-screen captures. 7. If diagnostic retention is necessary, make it explicit and opt-in, redact sensitive regions, and enforce a short retention period. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (8)

Tool Parameter Abuse

High
Category
Tool Misuse
Content
def run(cmd, timeout=15):
    log(f"→ {cmd[:120]}")
    result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=timeout)
    if result.returncode != 0 and result.stderr:
        log(f"  stderr: {result.stderr[:200]}")
    return result
Confidence
99% confidence
Finding
This is a concrete tool-parameter abuse issue: shell command construction is centralized in run(), and multiple higher-level functions feed it attacker-influenced parameters. Inputs like args.message flow into paste_text(), args.name can influence OCR search commands, and OCR-detected text is also reused in shell invocations, making command injection practical and dangerous on a macOS host with GUI automation privileges.

Lp3

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding
The skill invokes powerful local shell-based GUI automation tools but does not declare any explicit tool scope or permission boundaries. Because it also requires Accessibility and Screen Recording privileges, the absence of scoped permissions increases the risk of unintended command execution, opaque behavior, and abuse of highly sensitive desktop capabilities.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
This skill depends on Screen Recording and Accessibility access, which are highly sensitive OS permissions that can expose on-screen data and allow control of other applications. Failing to clearly warn users in the skill description can cause them to grant broad privileges without understanding the privacy and security implications.

Natural-Language Policy Violations

Medium
Confidence
92% confidence
Finding
The script forces OCR processing to use only Simplified Chinese, Traditional Chinese, and U.S. English via a fixed `recognitionLanguages` list. This is a natural-language policy concern because it imposes specific language/locale handling without offering the user a choice or documenting an opt-in.

Natural-Language Policy Violations

Medium
Confidence
83% confidence
Finding
The file-level natural-language instructions, usage text, and runtime-facing strings are entirely in Chinese, with no indication that the user can choose another language or that the locale restriction is intentional and documented as region-specific. Under the policy, forcing a specific language without user opt-in is a natural-language policy concern.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def run(cmd, timeout=15):
    log(f"→ {cmd[:120]}")
    result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=timeout)
    if result.returncode != 0 and result.stderr:
        log(f"  stderr: {result.stderr[:200]}")
    return result
Confidence
97% confidence
Finding
The helper wraps subprocess.run with shell=True and is later used to build commands from untrusted inputs such as the recipient name, message text, screenshot path, and OCR keyword. Because several callers interpolate user-controlled or OCR-derived strings directly into shell command strings, an attacker can inject shell metacharacters and execute arbitrary commands on the host running the automation.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
This script performs real GUI automation to locate chats and send messages with no interactive confirmation, dry-run mode, recipient preview, or final send prompt. In the context of a messaging skill, this can cause unintended message delivery, misdirected communication due to OCR mistakes, or abuse by upstream prompts that trigger the skill without the operator fully understanding that it will send live messages.

Natural-Language Policy Violations

Low
Confidence
82% confidence
Finding
The documentation includes a prescriptive instruction for Chinese input handling ('只用 ... paste 中文') and the overall skill description and trigger phrases are centered on Chinese usage without stating that this is optional or region-specific. This can be a language/locale policy issue because the skill appears to assume a specific language mode without user opt-in.

Static analysis

No suspicious patterns detected.