Back to skill

Security audit

Imou Open Device Operate

Security checks for vulnerabilities and agentic risk

Overview

The skill appears to do its stated camera-control job, but it needs review because unvalidated network endpoints can receive sensitive camera tokens and device data.

Install only if you explicitly want this agent to operate Imou/Lechange cameras. Use only the documented HTTPS Imou API base URLs, keep IMOU_APP_SECRET private, and avoid using --save on untrusted or unexpected snapshot responses until URL and file-size validation is added. Treat camera snapshots and PTZ movement as privacy-sensitive actions requiring user authorization.

Vulnerability Patterns
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (2)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/imou_client.py:33
Finding
Unrestricted API Base URL Can Expose Authentication Material and Administrative Device Tokens<![CDATA[ ## Vulnerability Details **File Location**: `scripts/imou_client.py:33-74` **Vulnerability Type**: Unvalidated security-sensitive network destination **Risk Level**: High ### Vulnerable Code ```python def _get_base_url(): return os.environ.get("IMOU_BASE_URL", "").strip() or DEFAULT_BASE_URL def _build_sign(time_sec: int, nonce: str, app_secret: str) -> str: """Build sign: MD5 of 'time:{time},nonce:{nonce},appSecret:{app_secret}' (UTF-8), 32-char lowercase hex.""" raw = f"time:{time_sec},nonce:{nonce},appSecret:{app_secret}" return hashlib.md5(raw.encode("utf-8")).hexdigest() def _request(method: str, params: dict, app_id: str, app_secret: str, base_url: str = None) -> dict: """ Send one Open API request. :param method: API method name (e.g. 'accessToken', 'setDeviceSnapEnhanced', 'controlMovePTZ'). :param params: Request params object. :param app_id: App ID. :param app_secret: App secret for sign. :param base_url: Optional base URL; uses env IMOU_BASE_URL or default if None. :return: Full response body as dict; check result.code for '0'. """ base = base_url or _get_base_url() url = f"{base.rstrip('/')}/openapi/{method}" time_sec = int(time.time()) nonce = uuid.uuid4().hex sign = _build_sign(time_sec, nonce, app_secret) body = { "system": { "ver": "1.0", "appId": app_id, "sign": sign, "time": time_sec, "nonce": nonce, }, "id": str(uuid.uuid4()), "params": params, } headers = { "Content-Type": "application/json", OPENCLAW_HEADER: OPENCLAW_HEADER_VALUE, } resp = requests.post(url, headers=headers, json=body, timeout=30) resp.raise_for_status() return resp.json() ``` The affected requests subsequently include administrative tokens and device identifiers: ```python out = _request( "setDeviceSnapEnhanced", { "token": token, ...[truncated 2833 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Parse the configured URL with a standards-compliant URL parser before constructing requests. 2. Require the `https` scheme and reject plaintext HTTP. 3. Allowlist only the documented API hostnames: - `openapi.lechange.cn` - `openapi-sg.easy4ip.com` - `openapi-fk.easy4ip.com` - `openapi-or.easy4ip.com` 4. Reject embedded credentials, fragments, unexpected query strings, IP-literal hosts, and ports other than the explicitly supported HTTPS port. 5. Disable redirects for API POST requests, or validate every redirect destination before following it. 6. If custom endpoints are operationally necessary, require an explicit unsafe-development option rather than trusting an ordinary environment variable. 7. Document accurately that the application secret is used locally to generate a signature, while the application ID, signature, access token, and device metadata are transmitted. 8. Assign the Imou application only the minimum server-side device permissions required for snapshots and PTZ control. 9. Revoke and rotate affected tokens and credentials if requests may have been sent to an untrusted endpoint. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/device_operate.py:54
Finding
Unvalidated Snapshot URL Enables Server-Side Request Forgery and Unbounded File Downloads<![CDATA[ ## Vulnerability Details **File Location**: `scripts/device_operate.py:54-65, 68-91` **Vulnerability Type**: Unvalidated outbound URL and unrestricted download size **Risk Level**: Medium ### Vulnerable Code ```python def _download_url_to_file(url: str, path: str) -> bool: """Download URL content to local file. Returns True on success.""" try: import requests headers = {"Client-Type": "OpenClaw"} resp = requests.get(url, headers=headers, timeout=60, stream=True) resp.raise_for_status() with open(path, "wb") as f: for chunk in resp.iter_content(chunk_size=8192): if chunk: f.write(chunk) return True except Exception as e: print(f"[ERROR] Download failed: {e}", file=sys.stderr) return False def cmd_snapshot(args): token = _ensure_token() r = set_device_snap_enhanced( token, args.device_serial, args.channel_id, base_url=BASE_URL or None, ) if not r.get("success"): print(f"[ERROR] Snapshot failed: {r.get('error', 'Unknown')}", file=sys.stderr) sys.exit(1) url = r.get("url", "") if not url: print("[ERROR] No snapshot URL returned.", file=sys.stderr) sys.exit(1) print(url) if getattr(args, "save", None): save_path = args.save if os.path.isdir(save_path): save_path = os.path.join(save_path, f"snap_{args.device_serial}_{args.channel_id}.jpg") if _download_url_to_file(url, save_path): print(f"[INFO] Saved to {save_path}", file=sys.stderr) ``` ### Technical Analysis When `--save` is used, the Skill trusts the snapshot URL returned by the API and passes it directly to `requests.get`. No validation is performed on the URL scheme, hostname, resolved IP address, port, or redirect destination. Because `requests` follows redirects by default for GET requests, validating only the first URL would als ...[truncated 2324 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Require snapshot URLs to use HTTPS. 2. Restrict downloads to documented Imou snapshot or CDN hostnames. If those hosts are dynamic, define and maintain an explicit trusted-domain policy. 3. Disable automatic redirects and validate each redirect target before following it. 4. Resolve the destination hostname and reject loopback, private, link-local, multicast, reserved, and unspecified address ranges for both IPv4 and IPv6. 5. Protect against DNS rebinding by ensuring the validated address is the address used for the connection, or by using a network layer with enforced egress policy. 6. Reject unexpected ports and URLs containing embedded credentials. 7. Set a strict maximum download size. Check `Content-Length` when present and independently count streamed bytes so chunked responses cannot bypass the limit. 8. Apply an overall transfer deadline in addition to connection and read timeouts. 9. Require an expected image MIME type and, where practical, verify the downloaded file signature before committing it. 10. Download into a securely created temporary file and atomically rename it only after successful validation. 11. Avoid silently replacing existing files. Use exclusive creation by default or require explicit overwrite confirmation. 12. Remove partial files when download or validation fails. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
Findings (11)

Tainted flow: 'url' from os.environ.get (line 82, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Content
try:
        import requests
        headers = {"Client-Type": "OpenClaw"}
        resp = requests.get(url, headers=headers, timeout=60, stream=True)
        resp.raise_for_status()
        with open(path, "wb") as f:
            for chunk in resp.iter_content(chunk_size=8192):
Confidence
92% confidence
Finding
The code downloads and saves a URL returned from an upstream API without validating the destination host, scheme, or content type. In this skill context, that creates an SSRF-style trust boundary issue: if the snapshot URL is malicious, compromised, or redirected, the tool can be induced to make arbitrary outbound requests and write attacker-controlled content to disk.

Credential Access

High
Category
Privilege Escalation
Content
| Field         | Type   | Description                    |
|---------------|--------|--------------------------------|
| accessToken   | String | Admin access token            |
| expireTime    | Long   | Expiry time in **seconds**    |

Token is valid for 3 days. Request a new one when it expires or when API returns TK1002.
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Lp3

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding
The skill requires environment secrets and network access to a cloud API, but it does not declare an explicit tool scope such as permissions or allowed-tools. That creates an authorization gap where an agent may invoke a network-capable skill without clear sandboxing or user/admin visibility into what external access and secret usage are required.

Vague Triggers

Medium
Confidence
88% confidence
Finding
The 'Use when' text is broad and does not clearly restrict activation to deliberate device-operation requests. Because this skill can move cameras and capture images, vague routing criteria increase the chance of accidental or contextually inappropriate invocation, leading to privacy and physical-environment impacts.

Vague Triggers

Medium
Confidence
92% confidence
Finding
The search aliases include broad single-word triggers like 'imou' and '乐橙' that can match ordinary discussion of the brand rather than an intent to control devices. In an agent ecosystem, ambiguous activation can cause unintended invocation of a skill that performs real-world actions such as PTZ movement or snapshot capture.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
This markdown file documents `setDeviceSnapEnhanced`, which returns a downloadable snapshot image URL from a device camera. The section describes the mechanics and URL validity but does not warn that invoking the endpoint captures and exposes potentially privacy-sensitive visual data.

External Transmission

Medium
Category
Data Exfiltration
Content
"Content-Type": "application/json",
        OPENCLAW_HEADER: OPENCLAW_HEADER_VALUE,
    }
    resp = requests.post(url, headers=headers, json=body, timeout=30)
    resp.raise_for_status()
    return resp.json()
Confidence
80% confidence
Finding
The client transmits sensitive control data and authentication material to a network endpoint, and the destination can be overridden via the IMOU_BASE_URL environment variable or function argument. If an attacker can influence that configuration, tokens, app identifiers, device identifiers, and signed requests could be exfiltrated to an attacker-controlled server, enabling surveillance-related abuse or credential misuse.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
This code performs a safety-relevant remote device control action by sending a network request to move or zoom a camera, but there is no confirmation prompt, logging, print statement, or cautionary comment warning about the action's real-world effect. The function docstring describes parameters and capability requirements, but does not disclose that invoking it will actively control the device.

Unpinned Dependencies

Low
Category
Supply Chain
Content
requests>=2.28.0
Confidence
95% confidence
Finding
The dependency is specified as `requests>=2.28.0`, which is not pinned to an exact version. This makes builds non-reproducible and can cause the skill to install different releases over time, including newly introduced vulnerable or incompatible versions. In a device-operation skill that likely performs network calls to cameras or cloud APIs, dependency drift increases supply-chain and reliability risk.

Unverifiable Dependency: requests has 16 known advisory(ies) (CVE-2014-1830 (Exposure of Sensitive Information to an Unauthorized Actor in Requests); CVE-2024-47081 (Requests vulnerable to .netrc credentials leak via malicious URLs); CVE-2024-35195 (Requests `Session` object does not verify requests after making first request wi) +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
87% confidence
Finding
Because `requests` is unpinned, it is impossible to verify whether the installed version includes fixes for known advisories affecting older releases. This is more concerning in a skill used for Imou/Lechange device control and snapshot download, since HTTP client flaws could expose credentials, mishandle TLS or redirects, or leak sensitive device data depending on how the library is used.

Natural-Language Policy Violations

Low
Confidence
97% confidence
Finding
The module docstring states "All descriptions and output in English," which is a natural-language locale constraint. The file does not offer any language choice or indicate that English is required for a justified region-specific purpose.

Static analysis

No suspicious patterns detected.