Back to skill

Security audit

Ulanzi TC001

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly supports TC001 clock control, but it should be reviewed because it includes under-disclosed external weather/AWTRIX network actions and can resend a YouTube API key over plaintext local HTTP.

Review this skill before installing. Use it only on a trusted local network, avoid storing valuable API keys on the TC001 unless you accept plaintext local HTTP exposure, and treat the weather/AWTRIX command as an extra network feature that should be documented, scoped, or removed before routine use.

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/tc001.py:36
Finding

Plaintext Transmission of YouTube API Key and Device Configuration

Content
View full analysis

Vulnerability Details

File Location: scripts/tc001.py:36-37, scripts/tc001.py:73-78, scripts/tc001.py:88-97, scripts/tc001.py:138-148, scripts/tc001.py:157-163, scripts/tc001.py:314-361
Vulnerability Type: Sensitive information transmitted over unencrypted HTTP
Risk Level: High

The device endpoint is constructed using HTTP rather than HTTPS:

python
HOST = load_host()
BASE = f"http://{HOST}"

The application settings include a YouTube API key:

python
APP_TEXT_FIELDS = [
    "cityCode",
    "bilibiliUid", "bilibiliAnimation", "bilibiliColor", "bilibiliFormat",
    "weiboUid", "weiboAnimation", "weiboColor", "weiboFormat",
    "youtubeUid", "youtubeApikey", "youtubeAnimation", "youtubeColor", "youtubeFormat",
    "douyinUid", "douyinAnimation", "douyinColor", "douyinFormat",
    "awtrixServer", "awtrixPort",
]

All supplied settings are form-encoded and sent to the configured HTTP destination:

python
def http_post(path: str, data: dict) -> str:
    body = urlencode(data).encode("utf-8")
    req = Request(BASE + path, data=body, method="POST")
    req.add_header("Content-Type", "application/x-www-form-urlencoded")
    with urlopen(req, timeout=5) as r:
        return r.read().decode("utf-8", errors="ignore")

Existing application settings, including the API key, are read into a dictionary and submitted again when settings are saved:

python
def load_app_settings():
    html = http_get("/app_switch")
    data = {}
    for name, field in GADGET_FIELDS.items():
        data[field] = "on" if _checkbox_checked(html, field) else ""
    for f in APP_TEXT_FIELDS:
        data[f] = _input_value(html, f)
    return data


def save_app_settings(data: dict):
    payload = {"page": "app_switch"}
    payload.update(data)
    return http_post("/app_switch", payload)

For example, changing a single gadget causes the complete application ...[truncated 2768 chars]

Remediation
View remediation

Remediation Suggestions

  1. Use authenticated HTTPS for device communication if supported by the device firmware.
  2. If the device only supports HTTP, clearly warn users that credentials must not be configured or transmitted over an untrusted network.
  3. Avoid resending youtubeApikey during unrelated updates. Prefer a dedicated API operation that sends only the field being changed.
  4. If the device requires complete form submissions, provide an explicit confirmation before retransmitting sensitive fields and document the network exposure.
  5. Validate the configured host. By default, restrict it to private or loopback addresses and require explicit opt-in for public destinations.
  6. Protect configuration files containing host or credential-related information with restrictive filesystem permissions.
  7. Never print sensitive values in command output, exceptions, or debug logs.
  8. Where supported by the external service, restrict the YouTube API key by API, quota, source, and billing controls, and rotate any key suspected of interception.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/tc001.py:364
Finding

External HTTPS Requests Disable Certificate Verification

Content
View full analysis

Vulnerability Details

File Location: scripts/tc001.py:364-377
Vulnerability Type: Improper certificate validation
Risk Level: Medium

The weather command explicitly creates an SSL context that does not verify server certificates and uses it for both external API requests:

python
def cmd_weather(args):
    # Geocode via Open-Meteo
    city = args.city
    city_q = urlencode({"name": city, "count": 1, "language": "pt", "format": "json"})
    geo_url = f"https://geocoding-api.open-meteo.com/v1/search?{city_q}"
    import ssl
    ctx = ssl._create_unverified_context()
    with urlopen(geo_url, timeout=8, context=ctx) as r:
        geo = json.loads(r.read().decode("utf-8"))
    if not geo.get("results"):
        print("City not found")
        sys.exit(1)
    loc = geo["results"][0]
    lat, lon = loc["latitude"], loc["longitude"]
    name = loc.get("name", city)

    wx_url = f"https://api.open-meteo.com/v1/forecast?latitude={lat}&longitude={lon}&current=temperature_2m,weather_code&timezone=America%2FSao_Paulo"
    with urlopen(wx_url, timeout=8, context=ctx) as r:
        wx = json.loads(r.read().decode("utf-8"))

Technical Analysis

ssl._create_unverified_context() disables certificate-chain and hostname verification. Although the URLs use HTTPS, the script therefore has no cryptographic assurance that it is communicating with Open-Meteo.

An active network attacker can present an arbitrary certificate and impersonate either external API endpoint. The returned JSON is parsed without authenticity validation, and selected values are subsequently formatted into a notification that is sent to the configured AWTRIX endpoint.

The weather command is also not described in SKILL.md, although it is exposed by the command-line parser. External geocoding and weather requests are not necessary for the primary declared function of controlling the TC001 through local HTTP, so ...[truncated 1361 chars]

Remediation
View remediation

Remediation Suggestions

  1. Remove ssl._create_unverified_context() and use urlopen() with Python's default verified TLS context.
  2. If an explicit context is required, create it with ssl.create_default_context() and retain hostname and certificate-chain verification.
  3. Ensure the runtime has an up-to-date trusted CA bundle rather than bypassing validation when certificate errors occur.
  4. Handle ssl.SSLCertVerificationError, network failures, malformed JSON, and missing response fields with clear errors and without retrying insecurely.
  5. Document the weather command and its disclosure of the city name to Open-Meteo, or remove the command if it is outside the Skill's intended functionality.
  6. Validate external response types and reasonable value ranges before constructing the AWTRIX notification.
  7. Continue using fixed, allowlisted API origins and do not permit untrusted input to control the URL scheme or hostname.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (12)

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

The documented purpose is controlling a local TC001 device, but the broader behavior includes external weather/geocoding access and control of a separate AWTRIX device. This mismatch is dangerous because users and policy engines may authorize the skill for harmless local-device control while it can also reach external services or manipulate other devices, creating a trust-boundary violation.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding

The skill advertises network, environment-variable, and file-based behavior but does not declare any tool scope or allowed-tools boundary. In an agent setting, missing explicit permission constraints can let the skill run with broader capabilities than users expect, increasing the chance of unauthorized local-network access, secret use from environment variables, or unintended file reads.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The document explicitly lists writable POST endpoints that modify device settings but does not clearly warn that these operations are state-changing or potentially disruptive. In the context of an agent skill designed to control a local device over HTTP, this increases the chance that an LLM-driven agent or user will issue configuration-changing requests without adequate confirmation, causing unauthorized or accidental changes to the TC001's behavior.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The code loads configuration for and later communicates with an AWTRIX host, which is a separate device outside the TC001-only scope. Undeclared control of another local device increases the attack surface and may cause unintended actions on infrastructure the user did not expect this skill to touch.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

This code performs HTTP GET/POST requests to a local device and later sends user-supplied city data to external weather services, but the file provides no docstring, comments explaining the privacy impact, or user-facing notice before transmission. For code files, network calls that transmit user or system data should have some visible disclosure unless the warning exists elsewhere, which is not shown here.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The save_sys_settings and save_app_settings functions issue POST requests that change device configuration, and many commands call them directly without a confirmation prompt or explicit warning about modifying remote device state. Aside from generic parser text, the file lacks user-facing disclosure that these actions write settings to the device.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The skill advertises local TC001 control, but it also performs internet weather lookups and sends notifications to a separate AWTRIX device. This hidden scope expansion violates least surprise and can leak user-supplied data or trigger actions on systems outside the declared device boundary.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The weather command makes outbound requests to Open-Meteo services even though the skill's stated purpose is local HTTP control of a TC001 device. That broadens the trust boundary, exposes user-provided location data externally, and creates undeclared dependency on internet services.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
95% confidence
Finding

This code transmits externally derived location parameters to an internet weather API from a skill that is supposed to perform local-only TC001 control. In context, the external transmission is dangerous because it is undeclared, expands data exposure, and can surprise users who expect no internet communication.

Content

Scanner excerpt · scripts/tc001.py (reported line 419)May include surrounding context.

python
lat, lon = loc["latitude"], loc["longitude"]
    name = loc.get("name", city)

    wx_url = f"https://api.open-meteo.com/v1/forecast?latitude={lat}&longitude={lon}&current=temperature_2m,weather_code&timezone=America%2FSao_Paulo"
    with urlopen(wx_url, timeout=8, context=ctx) as r:
        wx = json.loads(r.read().decode("utf-8"))
    temp = wx["current"]["temperature_2m"]

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
82% confidence
Finding

The documentation presents the configurable language values as only english|chinese, which is a natural-language locale constraint. Because the skill does not explain that this limitation is device-imposed or offer any user-choice framing, it can be read as enforcing a language policy without opt-in.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
98% confidence
Finding

The geocoding query hard-codes language='pt', which imposes a specific locale on returned place names regardless of user preference. The policy for natural-language behavior requires language choice, opt-in, or a clearly justified locale constraint, none of which appears in this file.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
95% confidence
Finding

The weather forecast request hard-codes timezone=America/Sao_Paulo, which enforces a locale-specific setting independent of the user's location or preference. This is a locale policy concern unless the skill offers a choice or documents a justified regional scope.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.