Back to skill

Security audit

Tuya Smart Control

Security checks for vulnerabilities and agentic risk

Overview

The skill is a real Tuya smart-home integration, but it needs review because it can control devices and handle camera media while endpoint, consent, and image-processing disclosures are too loose.

Install only if you are comfortable giving the skill a Tuya API key that can control devices, read home/device/location/statistics data, send self-notifications, monitor events, and capture camera media. Keep endpoints restricted to official Tuya hosts, require explicit confirmation for physical actions and camera capture, avoid unattended automation for sensitive devices, and ask the publisher to pin dependencies and fully disclose AI image-processing destinations.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/tuya_api.py:89
Finding
API Key Can Be Transmitted to an Arbitrary REST Endpoint<![CDATA[ ## Vulnerability Details **File Location**: `scripts/tuya_api.py:89-105, 121-136` **Vulnerability Type**: Unrestricted authenticated endpoint override **Risk Level**: High ### Vulnerable Code ```python if api_key is None: api_key = os.environ.get("TUYA_API_KEY") if base_url is None: base_url = os.environ.get("TUYA_BASE_URL") if not api_key: raise ValueError( "Missing API key. Set environment variable TUYA_API_KEY, " "or pass api_key argument." ) if not base_url: base_url = _resolve_base_url(api_key) self.api_key = api_key self.base_url = base_url.rstrip("/") self.timeout = timeout self.session = requests.Session() self.session.headers.update({ "Authorization": f"Bearer {api_key}", }) ``` ```python def _get(self, path: str, params: dict = None): """Send GET request and return the ``result`` field directly.""" url = f"{self.base_url}{path}" resp = self.session.get(url, params=params, timeout=self.timeout) resp.raise_for_status() data = resp.json() if not data.get("success"): raise TuyaAPIError(data.get("code"), data.get("msg")) return data.get("result") def _post(self, path: str, data: dict = None): """Send POST request and return the ``result`` field directly.""" url = f"{self.base_url}{path}" resp = self.session.post(url, json=data, timeout=self.timeout) ``` ### Technical Analysis The client accepts `TUYA_BASE_URL` from the environment without validating its scheme, hostname, port, or relationship to Tuya. The API key is installed as a session-wide bearer authorization header, so every request made through that session transmits the credential to the selected endpoint. Although sending an API key to an official Tuya endpoint is necessary for the declared functionality, allowing an unrestricted destination exceeds minimum privilege. A compromised launcher, environment configuration, generated wrapper script, or local process capable of changing the environment ...[truncated 1250 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Allow only the documented Tuya HTTPS hosts by default. 2. Parse the URL using `urllib.parse.urlparse` and require: - Scheme exactly `https` - No embedded username or password - An approved hostname - An approved port, normally 443 3. Reject IP literals, malformed hosts, plaintext HTTP, and deceptive suffixes such as `trusted.example.attacker.test`. 4. Disable cross-origin redirects or strip `Authorization` whenever a redirect changes the host. 5. If custom endpoints are needed for development, require an explicit unsafe-development option and a separate non-production credential. 6. Do not place the authorization header on a reusable session until endpoint validation succeeds. 7. Document the exact authorized destination list in the data-egress statement. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/tuya_device_mq_client.py:97
Finding
API Key Can Be Transmitted to an Arbitrary WebSocket Server<![CDATA[ ## Vulnerability Details **File Location**: `scripts/tuya_device_mq_client.py:97-110, 145-153` **Vulnerability Type**: Unrestricted authenticated WebSocket endpoint override **Risk Level**: High ### Vulnerable Code ```python def __init__(self, api_key: str = None, uri: str = None, device_ids: Optional[list[str]] = None): if api_key is None: api_key = os.environ.get("TUYA_API_KEY") if not api_key: raise ValueError( "Missing API key. Set environment variable TUYA_API_KEY, " "or pass api_key argument." ) if uri is None: uri = _resolve_ws_uri(api_key) self._uri = uri self._api_key = api_key ``` ```python async def connect(self): """Connect to the WebSocket and start listening for events. Automatically reconnects on transient failures. Stops on fatal close codes or server error messages. """ headers = {"Authorization": self._api_key} self._running = True logger.info("Connecting to %s ...", self._uri) try: async for websocket in websockets.connect( self._uri, additional_headers=headers): ``` ### Technical Analysis The constructor permits callers to supply any WebSocket URI. There is no enforcement of `wss://`, no hostname allowlist, and no warning or separate credential requirement for custom servers. `connect()` then sends the production API key in an authorization header to that URI. Automatic prefix-based resolution is appropriately restricted to known endpoints, but the unrestricted override bypasses that protection. This is particularly risky because the Skill instructs the agent to generate subscription and automation scripts, creating opportunities for an unsafe URI to be introduced through generated code or configuration. ### Attack Path 1. An attacker influences a generated automation script or configuration so that it passes `uri="wss://attacker.example"` to `TuyaDeviceMQClient`. 2. The client st ...[truncated 906 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Require WebSocket endpoints to use `wss://`. 2. Restrict production connections to the exact hosts in `_PREFIX_TO_WS_URI`. 3. Validate normalized hostnames and ports before constructing authorization headers. 4. Reject user information, IP literals, unexpected ports, and deceptive hostname suffixes. 5. Remove the public production-credential URI override, or place it behind an explicit development-only option. 6. Require separate, low-privilege test credentials for custom endpoints. 7. Add tests proving that arbitrary domains and `ws://` endpoints are rejected. 8. Avoid logging query strings if URI parameters may contain sensitive information. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/tuya_api.py:346
Finding
Camera Capture Helpers Implicitly Assert User Privacy Consent<![CDATA[ ## Vulnerability Details **File Location**: `scripts/tuya_api.py:346-347, 400-414, 418-420, 473-488` **Vulnerability Type**: Unsafe consent default for decrypted camera media **Risk Level**: High ### Vulnerable Code ```python def ipc_ai_capture_pic_resolve_with_wait( self, device_id: str, allocate_result: dict, user_privacy_consent_accepted: bool = True, home_id: str = None, poll_timeout: int = 30, retry_count: int = 3) -> dict: ``` ```python def ipc_ai_capture_pic_allocate_and_fetch( self, device_id: str, user_privacy_consent_accepted: bool = None, pic_count: int = None, home_id: str = None) -> dict: """Allocate a PIC capture then automatically wait and resolve. Args: device_id: Device ID user_privacy_consent_accepted: True for decrypted URLs (default True) pic_count: Number of snapshots (1-5) home_id: Optional home ID """ if user_privacy_consent_accepted is None: user_privacy_consent_accepted = True ``` ```python def ipc_ai_capture_video_resolve_with_wait( self, device_id: str, allocate_result: dict, user_privacy_consent_accepted: bool = True, home_id: str = None, poll_timeout: int = 120, retry_count: int = 3) -> dict: ``` ```python def ipc_ai_capture_video_allocate_and_fetch( self, device_id: str, video_duration_seconds: int = 10, user_privacy_consent_accepted: bool = None, home_id: str = None) -> dict: """Allocate a VIDEO capture then automatically wait and resolve. Args: device_id: Device ID video_duration_seconds: Video duration in seconds (1-60, default 10) user_privacy_consent_accepted: True for decrypted URLs (default True) home_id: Optional home ID """ if user_privacy_consent_accepted is None: user_privacy_consent_accepted = True ``` ### Technical Analysis The camera helper APIs treat an omitted consent decision a ...[truncated 1541 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Change all camera consent defaults to `False`. 2. Prefer requiring the consent argument with no default for any method capable of returning decrypted media. 3. Reject `None` rather than silently converting it to `True`. 4. Require a clear, current user confirmation before each sensitive capture or before the first capture in a narrowly defined session. 5. Separate capture allocation from decrypted-media resolution so consent is checked immediately before decryption. 6. Do not return encryption keys, storage object keys, or decrypted URLs unless the active request requires them. 7. Add tests verifying that omitted, false, malformed, or stale consent cannot yield decrypted URLs. 8. Update all SDK and Skill examples to pass consent explicitly only after user confirmation. ]]>

other

Error
Location
SKILL.md:257
Finding
Camera Images Are Sent to an Unspecified Vision Model Without Complete Egress Disclosure<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:257-269, 333-341` **Vulnerability Type**: Undisclosed sensitive camera-data egress **Risk Level**: High ### Vulnerable Instruction ```markdown ### Workflow 9: IPC Visual Recognition When the user asks "What's in front of my camera?", "Is there anyone at the door?", or "Describe what the camera sees": 1. **Capture a snapshot** — Follow Workflow 8 Steps 1-3 to take a PIC capture 2. **Get the image URL** — Extract `resolve["decrypt_image_url"]` from the capture result. If the resolve failed or returned `NOT_READY`, inform the user and stop 3. **Download the image** — Fetch the image content from the decrypted URL 4. **Send to AI vision model** — Pass the image to the AI large model for visual understanding. Describe the image content in natural language based on the user's question: - General question ("What's there?") → describe the overall scene, objects, and people - Specific question ("Is there a package?", "Is anyone at the door?") → focus on answering the specific question 5. **Return the description** — Respond to the user with the visual analysis result in conversational language ``` The corresponding disclosure is incomplete: ```markdown ## Data Egress Statement **This skill sends data to the Tuya Open Platform**: | Data Type | Sent To | Purpose | Required | |-----------|---------|---------|----------| | Api-key | User-configured base_url | API authentication | Required | | Device ID | User-configured base_url | Device query and control | Required | | Control commands | User-configured base_url | Device property issuance | Required | | Api-key | Auto-detected WebSocket URI | Real-time event subscription authentication | Required for message subscription | ``` ### Technical Analysis The visual-recognition workflow explicitly instructs the agent to download decrypted camera imagery and pass it to an AI vision model. However, the data-egress statement lists only Tuya destinations ...[truncated 1222 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Expand the data-egress statement to identify camera imagery as a transferred data category. 2. Identify the vision provider or clearly state that the destination depends on the host Agent configuration. 3. Obtain explicit informed consent before sending decrypted media to any separate processor. 4. Describe the processing purpose, retention expectations, and whether provider-side training is disabled. 5. Prefer an approved local vision model when available. 6. Remove metadata and avoid retaining downloaded images after analysis. 7. Provide a non-egress option that captures media without forwarding it for recognition. 8. Ensure the user's consent to camera decryption is distinct from consent to third-party vision processing. ]]>

T08 · Insecure Dependencies

Warning
Location
scripts/requirements.txt:1
Finding
Security-Sensitive Dependencies Are Not Reproducibly Pinned<![CDATA[ ## Vulnerability Details **File Location**: `scripts/requirements.txt:1-2` **Vulnerability Type**: Unpinned dependency versions and missing integrity verification **Risk Level**: Medium ### Vulnerable Configuration ```text requests>=2.28.0,<3.0.0 websockets>=12.0 ``` ### Technical Analysis The dependency specification permits installation of future releases that were not reviewed with this Skill. No lockfile, package hashes, or trusted-index restrictions are supplied. `websockets>=12.0` has no upper bound, and `requests` accepts any compatible release below 3.0. These libraries execute in the same Python process that stores `TUYA_API_KEY`, sends device commands, receives real-time device events, and handles camera-related data. Consequently, compromise of an accepted dependency release or package source would expose high-value credentials and smart-home capabilities. This finding does not establish that the named packages are currently malicious. The vulnerability is the non-reproducible, non-integrity-checked installation policy. ### Attack Path 1. A dependency account, distribution channel, package index, or future compatible release is compromised. 2. The Skill is installed or updated after the compromised version becomes the latest version satisfying the ranges. 3. The package manager resolves and installs the compromised release because no exact version or hash prevents it. 4. The malicious package executes when imported by the Skill. 5. It reads process credentials or intercepts REST/WebSocket traffic and exfiltrates data or alters commands. ### Impact Assessment A compromised dependency would run with the permissions of the Skill process. It could read environment variables, including `TUYA_API_KEY`; access device and camera data in memory; change API requests; capture notification contents; and access any files or network resources available to the Agent runtime. The scope therefore includes both the Tuya account and the local process ...[truncated 16 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin each dependency to an exact reviewed version. 2. Generate and commit a reproducible lockfile. 3. Use hash-verified installation, such as `pip install --require-hashes`. 4. Configure installation to use an approved package index. 5. Review transitive dependencies and include them in the lock and hash set. 6. Apply dependency updates through a controlled process with security review and automated tests. 7. Add an upper bound for `websockets` if exact pinning cannot immediately be adopted. 8. Run the Skill with restricted filesystem and network privileges to reduce supply-chain impact. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/tuya_api.py:520
Finding
Notification Contents Are Disclosed in Error Logs<![CDATA[ ## Vulnerability Details **File Location**: `scripts/tuya_api.py:520-539` **Vulnerability Type**: Insufficient redaction of sensitive command arguments **Risk Level**: Medium ### Vulnerable Code ```python def _redact_args(command: str, args: list) -> list: """Truncate notification message content in args for safe display.""" if command not in _SENSITIVE_COMMANDS: return args redacted = [] for arg in args: if isinstance(arg, str) and len(arg) > _MAX_ARG_DISPLAY_LEN: redacted.append(arg[:_MAX_ARG_DISPLAY_LEN] + "...<truncated>") else: redacted.append(arg) return redacted ``` ```python def _print_error(command: str, args: list, message: str, code: int = None): """Print a standardized error block to stderr.""" safe_message = _sanitize_message(message) safe_args = _redact_args(command, args) print(f"Error: {safe_message}", file=sys.stderr) print(f"Command: {command}", file=sys.stderr) print(f"Args: {json.dumps(safe_args, ensure_ascii=False)}", file=sys.stderr) if code is not None: print(f"TuyaErrorCode: {code}", file=sys.stderr) print(f"Suggestion: {_error_suggestion(code)}", file=sys.stderr) ``` ### Technical Analysis The function is named `_redact_args`, but it does not actually redact notification content. Messages of 80 characters or fewer are logged in full, while longer messages disclose their first 80 characters. Email and push subjects are also included. Error output may be captured by Agent runtimes, shell histories, centralized log systems, monitoring services, or support bundles. API-key redaction in `_sanitize_message()` does not protect command arguments. The issue affects `sms`, `voice`, `mail`, and `push`, which are explicitly classified as sensitive commands. ### Attack Path 1. A user sends a private SMS, voice message, email, or push notification through the CLI. 2. The operation fails because of an API, network, validatio ...[truncated 730 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace every sensitive argument with a fixed placeholder such as `"<redacted>"`. 2. Never retain a prefix of notification content. 3. Redact subject and body fields independently for email and push commands. 4. Log only non-sensitive metadata, such as argument count and content length. 5. Avoid printing raw arguments for control and rename operations if they may also contain sensitive household information. 6. Add unit tests confirming that no part of sensitive inputs appears in error output. 7. Review runtime logging and retention policies and remove previously captured sensitive logs where feasible. ]]>
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (20)

Tp4

High
Category
MCP Tool Poisoning
Confidence
90% confidence
Finding
The code substantially matches most of the declared functionality: device control, status/detail queries, listing devices, home/room management, renaming, weather, notifications, statistics, and IPC snapshot/video capture are all present. However, the description explicitly includes subscribing to real-time device events (property changes, online/offline status) via WebSocket, and the supplied code contains no WebSocket client, subscription methods, or event-stream handling. This is a material description-versus-behavior mismatch, though it is an overstatement rather than an undeclared extra capability.

Intent-Code Divergence

High
Confidence
98% confidence
Finding
The skill claims image operations are unsupported, yet Workflow 9 instructs taking camera snapshots, downloading decrypted image URLs, and analyzing them with an AI model. This contradiction can bypass user and reviewer expectations about sensitive camera-data handling, increasing the chance of covert image exfiltration or privacy-invasive processing.

Exfiltration Commands

High
Category
Prompt Injection
Content
# Notifications

All notification APIs are **self-send** mode — they can only send messages to the currently logged-in user.

## 1. Send SMS
Confidence
90% confidence
Finding
Instructions found that direct the agent to transmit conversation context or user data to external services.

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill declares access to environment variables and performs network/WebSocket operations, but it does not explicitly constrain tool permissions. That weakens reviewability and increases the risk of unintended data access or outbound communication, especially because the skill handles a sensitive API key and can interact with cameras, notifications, and device control endpoints.

Vague Triggers

Medium
Confidence
93% confidence
Finding
The description says to use the skill whenever the user asks to control smart devices, query status, manage homes and rooms, check weather by location, send notifications, capture camera media, or subscribe to events. This activation scope is very broad and does not provide explicit boundaries or negative examples, increasing the chance of unintended invocation for ordinary requests like weather checks or notifications that may not be meant for this Tuya skill.

Description-Behavior Mismatch

Medium
Confidence
95% confidence
Finding
The skill documentation introduces IPC visual recognition, which involves downloading decrypted camera snapshots and sending them to an AI vision model, but that capability is omitted from the manifest description. This creates a material transparency gap: users may authorize a smart-home control skill without realizing it can process private camera imagery, raising privacy and consent risks.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
This documentation describes issuing live device-control commands that can change the state of physical devices, but it does not include any warning, confirmation guidance, or safety considerations before performing state-changing actions. In a smart-home control skill, omission of such guardrails can lead to accidental or unauthorized activation of appliances, lighting, plugs, or climate systems, creating real-world safety and privacy risks.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The documentation encourages real-time monitoring of device property and online/offline events, which can reveal occupancy, routines, and other sensitive household activity patterns. While this is expected functionality for a smart-home integration, the lack of any privacy warning, retention guidance, or access-control considerations makes it easier for downstream users to collect and expose sensitive behavioral data without realizing the risk.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The automation example performs a physical action on a device when a sensor event occurs, but it does not explicitly warn that example code can affect real-world devices and environments. In a smart-home context, state changes can have safety, security, nuisance, or cost implications if copied blindly or adapted to more sensitive devices, especially when triggered automatically from noisy or spoofed events.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
This documentation enables capture and retrieval of camera snapshots/videos, which are highly privacy-sensitive, but the warning about consent appears later in the document rather than as an upfront gating requirement. In the context of a natural-language smart-home control skill, this increases the risk that an agent or integrator could implement covert or insufficiently consented surveillance flows using decrypted media URLs.

Natural-Language Policy Violations

Medium
Confidence
94% confidence
Finding
The file explicitly states that `w.condition.0` is an English description and repeats that `w.condition` is an English-only weather condition description. Under the policy, forcing a specific language without user opt-in is a natural-language locale violation, and this documentation does not offer any language choice or explain a necessary regional constraint.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The IPC capture helpers can trigger camera snapshots and videos and, by default, set user_privacy_consent_accepted to True when no explicit value is provided. In the context of a natural-language smart home control skill, this creates a real privacy risk because an agent or caller could collect sensitive in-home camera data without a clear user-facing confirmation or friction at the point of action.

Vague Triggers

Low
Confidence
85% confidence
Finding
The monitoring workflow activates on phrases like "monitor device changes in real time" or "watch for property updates," which are generic enough to match many non-Tuya contexts. The document does not state limiting conditions such as requiring an existing Tuya device scope or explicit Tuya context before activation.

Missing User Warnings

Low
Confidence
85% confidence
Finding
This markdown file documents an operation that changes a device's custom name, which affects user-managed device metadata. The description presents the action plainly but does not include any warning or note that the operation updates persisted device information.

Missing User Warnings

Low
Confidence
85% confidence
Finding
This markdown file documents APIs that return a user's full device inventory, room/home associations, and current device property states such as presence-related or behavioral signals. The description does not include any user warning or privacy note about accessing potentially sensitive household/device metadata, which falls under missing warnings for markdown files.

Missing User Warnings

Low
Confidence
80% confidence
Finding
The markdown describes returning precise home latitude and longitude values and suggests reusing them for another API, but it does not include any warning that this is sensitive location data. For a skill/reference document, location-related behavior can affect user privacy and should be disclosed clearly.

Missing User Warnings

Low
Confidence
77% confidence
Finding
This documentation states that hourly statistics for all devices under the current user are queried and that user identity is automatically obtained from the login context, but it does not include any user-facing warning about accessing potentially sensitive usage data. For markdown files, SQP-2 applies when the skill description omits warnings about behavior that could affect user data or privacy.

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
89% confidence
Finding
`requests` is constrained only to a broad version range, so it is not possible to verify from this manifest whether deployment will select a version affected by known CVEs. Because this skill likely performs authenticated outbound API calls to Tuya services, unresolved dependency versions can expose it to credential leakage, TLS/verification issues, or other client-side flaws if an affected release is installed.

Unpinned Dependencies

Low
Category
Supply Chain
Content
requests>=2.28.0,<3.0.0
websockets>=12.0
Confidence
96% confidence
Finding
The dependency `websockets>=12.0` is not fully pinned, so builds may resolve to different versions over time, including newly introduced vulnerable or breaking releases. In a smart-home control skill that may maintain persistent WebSocket connections for real-time device events, supply-chain drift and unreviewed upgrades increase the risk of denial of service or exposure to library-level flaws.

Unverifiable Dependency: websockets has 4 known advisory(ies) (CVE-2018-1000518 (websockets is vulnerable to denial of service by memory exhaustion); CVE-2021-33880 (Observable Timing Discrepancy in aaugustin websockets library); CVE-2018-1000518 (aaugustin websockets version 4 contains a CWE-409: Improper Handling of Highly C) +1 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
90% confidence
Finding
`websockets` is also left unpinned, making it impossible to determine whether the installed version is affected by published vulnerabilities. Given the skill advertises WebSocket-based real-time device event subscriptions, a vulnerable `websockets` release could increase exposure to denial-of-service or protocol-handling weaknesses in a component central to its functionality.

Static analysis

No suspicious patterns detected.