Back to skill

Security audit

roku

Security checks for vulnerabilities and agentic risk

Overview

This Roku-control skill is not clearly malicious, but it includes undocumented Telegram remote-control code and broad local command handling that users should review before installing.

Review this skill carefully before installing. It controls a real Roku device and includes an undocumented Telegram bot path that can send commands into a local Roku daemon; only use it if you understand and secure the bot token, restrict who can interact with the bot, keep any bridge bound to localhost with a strong token, and are comfortable with unpinned third-party installs.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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
Findings (3)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
roku-telegram.py:32
Finding
Telegram callbacks permit unauthorized remote control of the Roku device## Vulnerability Details **File Location**: `roku-telegram.py:32-52` **Vulnerability Type**: Missing sender and chat authorization **Risk Level**: High ### Vulnerable Code ```python url = f"https://api.telegram.org/bot{TOKEN}/getUpdates" params = {"offset": offset, "timeout": 10} if offset else {"timeout": 10} resp = requests.get(url, params=params, timeout=15) data = resp.json() if data.get("ok"): for update in data["result"]: offset = update["update_id"] + 1 if "callback_query" in update: cb = update["callback_query"]["data"] if cb.startswith("roku_"): btn = cb.replace("roku_", "") print(f"→ {btn}", flush=True) send_to_roku(btn) # Answer callback cb_id = update["callback_query"]["id"] requests.post( f"https://api.telegram.org/bot{TOKEN}/answerCallbackQuery", json={"callback_query_id": cb_id} ) ``` ### Technical Analysis The Telegram poller treats every callback whose data starts with `roku_` as an authorized Roku command. It does not verify any of the identity or context fields supplied by Telegram, such as: - `callback_query.from.id` - The originating message's chat ID - Chat type - A configured user or chat allowlist The prefix check is command-format validation, not authorization. Any Telegram account capable of interacting with the configured bot can therefore submit accepted callbacks. The code transmits the callback-query identifier to Telegram's official `answerCallbackQuery` endpoint. This is expected Telegram bot protocol behavior, and the reviewed code does not send local files, Roku data, or unrelated environment variables to another endpoint. However, the bot token is embedded in the request URL and could be exposed by verbose HTTP, proxy, or except ...[truncated 1597 chars]
Remediation
## Remediation Suggestions 1. Require explicit Telegram user and chat allowlists, configured through protected settings: ```python allowed_users = {int(value) for value in os.environ["TELEGRAM_ALLOWED_USERS"].split(",")} allowed_chats = {int(value) for value in os.environ["TELEGRAM_ALLOWED_CHATS"].split(",")} ``` 2. Before processing callback data, require both the sender ID and originating chat ID to match the allowlists. 3. Reject callbacks without an associated message or other expected context. 4. Validate callback data against a fixed command allowlist rather than accepting every `roku_` prefix. 5. Restrict the bot's discoverability and interaction settings where Telegram supports doing so. 6. Do not log full Telegram request URLs because they contain the bot token. 7. Add explicit documentation warning that the Telegram poller creates a remote control channel. 8. Add authorization tests covering unauthorized users, unauthorized chats, malformed updates, and callbacks without messages.

T09 · Insecure Skill Coding Practices

Warning
Location
roku-listener.py:39
Finding
FIFO input can dynamically invoke overbroad Roku SDK methods## Vulnerability Details **File Location**: `roku-listener.py:39-50` **Vulnerability Type**: Unsafe dynamic method dispatch **Risk Level**: Medium ### Vulnerable Code ```python # Handle roku_* callbacks if line.startswith("roku_"): btn = line.replace("roku_", "") if hasattr(r, btn): getattr(r, btn)() print(f"→ {btn}", flush=True) # Handle "btn NAME" format elif line.startswith("btn "): btn = line.split(" ", 1)[1] if hasattr(r, btn): getattr(r, btn)() print(f"→ {btn}", flush=True) ``` ### Technical Analysis The listener derives an attribute name directly from FIFO-controlled input and invokes it through `getattr`. The `hasattr` check establishes only that the attribute exists; it does not establish that the attribute is an approved remote-control operation, is callable, or is safe to invoke without arguments. This exposes every compatible zero-argument public method available on the installed `Roku` object rather than the limited set of remote-control commands required by the Skill. The accessible surface may also change when the unpinned `roku` dependency is updated. The alternate implementation in `roku-daemon.py` demonstrates the safer design by mapping input through an explicit `BUTTON_MAP`. The generic listener does not apply an equivalent allowlist. ### Attack Path 1. An attacker or compromised local process obtains write access to `/tmp/roku-control`. 2. The attacker inspects or predicts methods exposed by the installed `python-roku` object. 3. The attacker writes input such as `btn METHOD_NAME` or `roku_METHOD_NAME` to the FIFO. 4. The listener resolves the attacker-selected name using `getattr`. 5. If the attribute exists, the listener invokes it without checking that it is an approved operation. 6. The method executes with the listener's network access and Roku-control authority. The Telegram poller can also feed `btn` values into this li ...[truncated 810 chars]
Remediation
## Remediation Suggestions 1. Replace dynamic attribute selection with an immutable command map: ```python BUTTON_MAP = { "up": "up", "down": "down", "left": "left", "right": "right", "ok": "select", "back": "back", "home": "home", "play": "play", } ``` 2. Resolve only names present in that map and reject every other input. 3. Validate the complete message format, including length and allowed character set. 4. Confirm the mapped attribute is callable before invoking it. 5. Use the same command parser and allowlist across the daemon, listener, client, and Telegram integration. 6. Replace the bare `except` block with narrow exception handling and security-relevant logging. 7. Add tests proving that internal, unknown, non-callable, and newly introduced SDK attributes cannot be invoked.

T08 · Insecure Dependencies

Warning
Location
SKILL.md:9
Finding
Installation instructions execute unpinned third-party package releases## Vulnerability Details **File Locations**: `SKILL.md:9-15`, `README.md:12-21` **Vulnerability Type**: Unpinned and mutable third-party dependencies **Risk Level**: Medium ### Vulnerable Code From `SKILL.md`: ```yaml metadata: {"clawdbot":{"emoji":"📺","requires":{"bins":["roku"]},"install":[{"id":"node","kind":"node","package":"@gumadeiras/roku","bins":["roku"],"label":"Install Roku (npm)"}]}} ``` ```bash npm install -g @gumadeiras/roku@latest ``` From `README.md`: ```bash pip3 install roku ``` ### Technical Analysis Both documented installation paths resolve mutable third-party package versions at installation time: - The npm command explicitly requests the `latest` release. - The Python command provides no version constraint or integrity hash. - The metadata package reference is also not pinned to a reviewed version. The effective code installed by these commands can therefore differ from the code reviewed during this audit. Package lifecycle scripts and imported runtime modules execute with the privileges of the installing or running account. The package names correspond to the project's declared Roku functionality, and the audit found no evidence of typosquatting or a currently malicious dependency. The risk arises from unsafe, non-reproducible dependency selection and possible future upstream compromise. ### Attack Path 1. An upstream package account, release process, registry entry, or maintainer environment is compromised, or a future release becomes malicious. 2. The attacker publishes a new version selected by `@latest` or by the unconstrained `pip3 install roku` command. 3. A user follows the installation instructions after the malicious release is available. 4. The package manager downloads the changed release without checking it against an audit-approved version or integrity value. 5. Malicious installation hooks or runtime code execute with the privileges of the user performing i ...[truncated 755 chars]
Remediation
## Remediation Suggestions 1. Replace `@latest` with a specifically reviewed npm version. 2. Pin the Python dependency to a reviewed version rather than using an unconstrained package name. 3. Commit lockfiles for supported installation paths. 4. Use package-manager integrity fields and hashes where supported. 5. For Python deployments, use hash-checked requirements such as `pip install --require-hashes -r requirements.txt`. 6. Document an explicit dependency update process that includes code review, changelog review, and security testing. 7. Avoid recommending installation with elevated privileges. 8. Ensure the version declared in Skill metadata matches the reviewed and documented version.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
Findings (22)

YARA rule 'agent_skill_credential_exfiltration_webhook': AI agent skill credential harvesting followed by webhook or external exfiltration [agent_skills]

Critical
Category
YARA Match
Content
#!/usr/bin/env python3
"""Roku Telegram poller - receives button presses directly."""

import os
import sys
import time
import json
import requests

TOKEN = os.environ.get("TELEGRAM_TOKEN", "")
PIPE_PATH = "/tmp/roku-control"
POLL_INTERVAL = 0.5

if not TOKEN:
    print("Set TELEGRAM_TOKEN")
    sys.exit(1)

if not os.path.exists(PIPE_PATH):
    print("Roku daemon not running")
    sys.exit(1)

def send_to_roku(btn):
    with open(PIPE_PATH, 'w') as f:
        f.write(f"btn {btn}\n")

def main():
    offset = None
    print("Polling started", flush=True)
    
    while True:
        try:
            url = f"https://api.telegram.org/bot{TOKEN}/getUpdates"
            params = {"offset": offset, "timeout": 10} if offset else {"timeout": 10}
Confidence
85% confidence
Finding
YARA rule matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).

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

Critical
Category
Data Flow
Content
url = f"https://api.telegram.org/bot{TOKEN}/getUpdates"
            params = {"offset": offset, "timeout": 10} if offset else {"timeout": 10}
            
            resp = requests.get(url, params=params, timeout=15)
            data = resp.json()
            
            if data.get("ok"):
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

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

Critical
Category
Data Flow
Content
# Answer callback
                            cb_id = update["callback_query"]["id"]
                            requests.post(
                                f"https://api.telegram.org/bot{TOKEN}/answerCallbackQuery",
                                json={"callback_query_id": cb_id}
                            )
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The code is clearly related to Roku control, so the general domain matches the description. However, the declared description specifically advertises a CLI with discovery, remote control, app launching, search, and HTTP bridge mode for real-time control. This code chunk instead implements a persistent background daemon that listens on /tmp/roku-control and forwards roku_* commands to a Roku device using a preconfigured IP address from ROKU_IP. It supports remote button presses and app launching, but there is no HTTP server/bridge, no device discovery, and no substantive search implementation beyond forwarding a 'search' command if supported by the Roku library. Therefore the description overstates and materially misrepresents the behavior of this supplied code chunk.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The implemented code only supports a narrow subset of the declared functionality: button-based remote control. It does not perform Roku discovery, app launching, search, or provide any HTTP bridge mode. There is no evidence of unrelated or more sensitive behavior, but the declared description materially overstates the implemented capabilities in this code chunk.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The declared description presents a CLI-based Roku control tool with broader device-management features such as discovery, app launching, search, and an HTTP bridge. The supplied code chunk only outputs a Telegram-formatted button layout for a Roku remote and relies on external routing/daemon behavior for any actual control. This is a materially different primary behavior and introduces an undeclared Telegram integration. While some remote-control intent overlaps conceptually, the implementation shown does not match the declared CLI and bridge-oriented functionality.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The declared description says this skill controls Roku devices via a CLI with discovery, remote control, app launching, search, and an HTTP bridge mode. The supplied code does not implement CLI control, device discovery, app launching, search, or an HTTP bridge. Instead, its primary function is to poll Telegram for bot callback events and forward extracted button presses to a local pipe consumed by another process. That is a materially different trigger path and capability set from the declared purpose. While forwarding button presses may support Roku control indirectly, the undeclared Telegram integration and pipe-based IPC indicate a significant description-to-behavior mismatch.

Lp3

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding
The skill advertises installation and operation of a CLI that clearly requires network access and persistent service behavior, but it does not declare any tool scope or permissions boundaries. This weakens user and platform understanding of what the skill can do, making unintended network, file, or environment access easier to hide or misuse.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The documentation encourages installing a persistent HTTP bridge that accepts command requests, but it does not prominently warn that this creates a standing control interface on the host. Even when bound to localhost in examples, local malware, other users, misconfiguration, or future binding changes could abuse the interface to control devices or leverage the service as a foothold.

External Transmission

Medium
Category
Data Exfiltration
Content
```bash
# Send key
curl -X POST http://127.0.0.1:19839/key \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer secret" \
  -d '{"key":"home"}'
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
- Roku must be on the same network as the CLI
- Bridge service runs as a native launchd (macOS) or systemd (Linux) service
- Use `--user` flag for user-space service (no sudo required)
- Use `--token` for authentication in bridge mode

## Source
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Context-Inappropriate Capability

Medium
Confidence
97% confidence
Finding
The listener is intended to process callback-style remote commands, but it accepts arbitrary Roku object method names from a world-accessible-looking path under /tmp and invokes them directly. In the context of a device-control skill, this increases risk because the skill bridges local IPC to network-capable device actions, so a local attacker or another untrusted skill/process could drive unintended Roku behavior or abuse non-button APIs.

Description-Behavior Mismatch

Medium
Confidence
93% confidence
Finding
This file adds Telegram bot polling and remote-control capability, which is materially broader than a Roku CLI/HTTP bridge description. That scope expansion creates an undocumented external control channel into the device controller, increasing attack surface and making operator review and consent harder.

Context-Inappropriate Capability

Medium
Confidence
90% confidence
Finding
The skill consumes Telegram credentials for a third-party messaging integration that is not justified by the stated purpose, creating an unexpected dependency on an external service. In agent-skill contexts, hidden or weakly documented credential use is dangerous because it can silently enable remote control and data flows users did not intend.

External Transmission

Medium
Category
Data Exfiltration
Content
while True:
        try:
            url = f"https://api.telegram.org/bot{TOKEN}/getUpdates"
            params = {"offset": offset, "timeout": 10} if offset else {"timeout": 10}
            
            resp = requests.get(url, params=params, timeout=15)
Confidence
89% confidence
Finding
This code establishes continuous outbound polling to Telegram, creating a live external command channel into the Roku controller. In the context of a skill described as a local CLI/HTTP bridge, that hidden egress meaningfully increases exposure because anyone with access to the Telegram bot can influence local device actions.

External Transmission

Medium
Category
Data Exfiltration
Content
# Answer callback
                            cb_id = update["callback_query"]["id"]
                            requests.post(
                                f"https://api.telegram.org/bot{TOKEN}/answerCallbackQuery",
                                json={"callback_query_id": cb_id}
                            )
Confidence
80% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
# Answer callback
                            cb_id = update["callback_query"]["id"]
                            requests.post(
                                f"https://api.telegram.org/bot{TOKEN}/answerCallbackQuery",
                                json={"callback_query_id": cb_id}
                            )
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Missing User Warnings

Low
Confidence
82% confidence
Finding
The markdown advertises device discovery, playback/navigation control, text entry, and app launching, but does not include any user-facing caution that these commands send control traffic to a Roku on the network and can immediately change device state. For a skill that interacts with a user-owned device over the network, a brief warning about operational impact would improve safety and user awareness.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Content
btn = BUTTON_MAP.get(action)
        if btn and hasattr(r, btn):
            try:
                getattr(r, btn)()
                print(f"Pressed: {action}", flush=True)
            except Exception as e:
                print(f"Button failed: {e}", file=sys.stderr)
Confidence
50% confidence
Finding
Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Content
from roku import Roku
    r = Roku(IP)
    getattr(r, btn_map[cb])()

if __name__ == "__main__":
    main()
Confidence
50% confidence
Finding
Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Content
if line.startswith("roku_"):
                            btn = line.replace("roku_", "")
                            if hasattr(r, btn):
                                getattr(r, btn)()
                                print(f"→ {btn}", flush=True)
                        # Handle "btn NAME" format
                        elif line.startswith("btn "):
Confidence
93% confidence
Finding
The listener takes attacker-controlled text from a local pipe and uses it as a method name on the Roku object, allowing invocation of any callable attribute exposed by the library rather than a fixed remote-control command set. Even though this is local IPC, any local process able to write to /tmp/roku-control can expand behavior beyond intended button presses, potentially triggering network actions or unexpected device operations.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Content
elif line.startswith("btn "):
                            btn = line.split(" ", 1)[1]
                            if hasattr(r, btn):
                                getattr(r, btn)()
                                print(f"→ {btn}", flush=True)
            except:
                time.sleep(0.01)
Confidence
93% confidence
Finding
This second dispatch path has the same issue: untrusted pipe input after the 'btn ' prefix becomes a method name invoked on the Roku object. That broadens the attack surface from documented button events to arbitrary exposed methods, enabling misuse by any local writer to the pipe.

Static analysis

No suspicious patterns detected.