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

Content
View full analysis

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
View 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

Content
View full analysis

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
View 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

Content
View full analysis

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
View 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
Confidence
85% confidence
Finding

YARA rule matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).

Content

Scanner excerpt · roku-telegram.py (reported line 10)May include surrounding context.

python
#!/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}

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

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · roku-telegram.py (reported line 35)May include surrounding context.

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"):

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

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · roku-telegram.py (reported line 51)May include surrounding context.

python
# Answer callback
                            cb_id = update["callback_query"]["id"]
                            requests.post(
                                f"https://api.telegram.org/bot{TOKEN}/answerCallbackQuery",
                                json={"callback_query_id": cb_id}
                            )

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.

Content

No source excerpt is available for this finding.

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.

Content

No source excerpt is available for this finding.

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.

Content

No source excerpt is available for this finding.

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.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

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.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · SKILL.md (reported line 71)May include surrounding context.

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"}'

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
70% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · SKILL.md (reported line 128)May include surrounding context.

md
- 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

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
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.

Content

Scanner excerpt · roku-telegram.py (reported line 32)May include surrounding context.

python
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)

External Transmission

Medium
Category
Data Exfiltration
Confidence
80% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · roku-telegram.py (reported line 51)May include surrounding context.

python
# Answer callback
                            cb_id = update["callback_query"]["id"]
                            requests.post(
                                f"https://api.telegram.org/bot{TOKEN}/answerCallbackQuery",
                                json={"callback_query_id": cb_id}
                            )

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · roku-telegram.py (reported line 52)May include surrounding context.

python
# Answer callback
                            cb_id = update["callback_query"]["id"]
                            requests.post(
                                f"https://api.telegram.org/bot{TOKEN}/answerCallbackQuery",
                                json={"callback_query_id": cb_id}
                            )

Missing User Warnings

Low
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Confidence
50% confidence
Finding

Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Content

Scanner excerpt · roku-daemon.py (reported line 79)May include surrounding context.

python
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)

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Confidence
50% confidence
Finding

Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Content

Scanner excerpt · roku-fast.py (reported line 32)May include surrounding context.

python
from roku import Roku
    r = Roku(IP)
    getattr(r, btn_map[cb])()

if __name__ == "__main__":
    main()

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
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.

Content

Scanner excerpt · roku-listener.py (reported line 44)May include surrounding context.

python
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 "):

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
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.

Content

Scanner excerpt · roku-listener.py (reported line 50)May include surrounding context.

python
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)

Static analysis

No suspicious patterns detected.