Back to skill

Security audit

Tapo Camera

Security checks for vulnerabilities and agentic risk

Overview

The skill is coherently designed for local Tapo camera snapshots, but its helper can send camera authentication material to any supplied host unless the user or agent enforces the LAN camera boundary.

Install only if you are comfortable using it for your own local Tapo cameras. Before running the helper, verify the camera IP or hostname yourself, avoid public or ambiguous hostnames, keep credentials in short-lived environment variables or a secret manager, and do not use --show-rtsp unless you need the credential-bearing URL for another local tool.

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

T09 · Insecure Skill Coding Practices

Error
Location
tapo-capture.py:20
Finding
Unrestricted Camera Host May Receive Sensitive Authentication Material## Vulnerability Details **File Location**: `tapo-capture.py:20-128` **Vulnerability Type**: Unvalidated destination for credential-bearing authentication **Risk Level**: High ### Vulnerable Code ```python parser.add_argument("--host", required=True, help="Camera hostname or IP") ``` ```python username = os.getenv("TAPO_CAMERA_USERNAME") password = os.getenv("TAPO_CAMERA_PASSWORD") credentials_hash = os.getenv("KASA_CREDENTIALS_HASH") if credentials_hash and (username or password): raise SystemExit( "Use either TAPO_CAMERA_USERNAME/TAPO_CAMERA_PASSWORD or KASA_CREDENTIALS_HASH, not both." ) if username or password: if not username or not password: raise SystemExit( "Both TAPO_CAMERA_USERNAME and TAPO_CAMERA_PASSWORD are required together." ) credentials = Credentials(username=username, password=password) else: credentials = None ``` ```python return DeviceConfig( host=host, timeout=timeout, credentials=credentials, credentials_hash=credentials_hash, connection_type=camera_connection, ) ``` ```python config = build_config(args.host, args.timeout) dev = await Device.connect(config=config) ``` ### Technical Analysis The mandatory `--host` argument is accepted without validating that it identifies a user-approved camera on the trusted local network. The value is inserted directly into a `DeviceConfig` alongside either the camera username and password or `KASA_CREDENTIALS_HASH`, after which `python-kasa` initiates authentication with the selected destination. This implementation does not enforce the LAN-only boundary documented elsewhere in the project. It does not: - Resolve and classify the supplied address before authentication. - Reject public, unspecified, multicast, loopback, or otherwise unauthorized destinations. - Compare the destination against an allowlist of approved camera addresses. ...[truncated 2304 chars]
Remediation
## Remediation Suggestions 1. **Resolve and validate the destination before loading or using credentials.** - Resolve the supplied hostname with `socket.getaddrinfo`. - Parse every result with Python's `ipaddress` module. - Require the address to belong to an explicitly approved LAN subnet rather than relying only on `is_private`. - Reject public, unspecified, multicast, reserved, and loopback addresses. Handle link-local addresses only through a documented, explicit exception. 2. **Use an explicit camera allowlist.** - Store user-approved camera IP addresses or stable device identifiers without credentials. - Require confirmation before adding a new destination. - Refuse credential-bearing connections to hosts absent from the allowlist. 3. **Mitigate DNS rebinding and time-of-check/time-of-use issues.** - Resolve the hostname once. - Validate the resolved address. - Connect to the validated address or otherwise pin resolution for the connection. - If hostname-based TLS verification is required, bind the validated IP and expected hostname securely rather than performing an independent second lookup. 4. **Validate before reading secrets where practical.** - Complete destination authorization before retrieving credentials from environment variables or a secret manager. - Keep secrets scoped to the shortest possible execution period. 5. **Fail closed on ambiguous resolution.** - Reject hostnames that resolve to a mixture of approved and unapproved addresses. - Report the resolved address without printing credentials or authenticated URLs. 6. **Add security regression tests.** - Confirm that approved RFC1918 camera addresses are accepted only when they are in configured LAN ranges. - Confirm that public IP addresses, loopback, multicast, unspecified addresses, mixed DNS results, and rebinding-style resolution changes are rejected before `Device.connect()` is called.
Vulnerability Patterns
  • 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
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (3)

Lp3

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding
The skill describes capabilities that involve shell execution, access to environment variables, and local network communication, but it does not declare an explicit tool scope such as permissions or allowed-tools. Even though the prose includes safety boundaries, those are not enforceable controls, so an agent runtime could grant broader-than-necessary access and execute discovery, ffmpeg, or credential-handling steps without a machine-checkable restriction set.

Vague Triggers

Medium
Confidence
91% confidence
Finding
The skill leaves activation scope open-ended by asking whether it should activate on broad topics like Tapo cameras, RTSP, ONVIF, or snapshots without defining default exclusions or requiring explicit confirmation before future activation. In an agent setting, this can cause the skill to engage in later conversations more broadly than the user intended, increasing the risk of collecting device details, creating files, or steering camera-related actions when the user did not explicitly request this skill.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
str(output),
    ]
    try:
        subprocess.run(cmd, check=True)
    except subprocess.CalledProcessError as exc:
        raise SystemExit(f"ffmpeg capture failed with exit code {exc.returncode}.") from exc
    return output
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Static analysis

No suspicious patterns detected.