Back to skill

Security audit

Radarr+

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches its Radarr movie-request purpose, but it includes an overbroad downloader that can fetch any URL and overwrite any writable path, plus weak credential-transport guidance.

Review before installing. Use only with trusted chat allowlists, prefer HTTPS Radarr/Plex URLs, avoid optional Plex unless you intend that credential use, and do not let untrusted input control fetch_asset.py URLs or output paths. Expect the skill to store movie tracking state and chat targets locally for progress notifications.

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/fetch_asset.py:20
Finding

Unrestricted Remote Asset Retrieval and Arbitrary File Overwrite

Content
View full analysis
int: ap = argparse.ArgumentParser(prog="fetch_asset") ap.add_argument("--url", required=True) ap.add_argument("--out", required=True) args = ap.parse_args(argv) out_path = pathlib.Path(args.out) out_path.parent.mkdir(parents=True, exist_ok=True) req = urllib.request.Request(args.url, headers={"User-Agent": "openclaw-radarr-skill"}) with urllib.request.urlopen(req, timeout=60) as resp: data = resp.read() out_path.write_bytes(data) ``` ### Technical Analysis The script accepts an unrestricted source URL and unrestricted destination path. Although the documented use is to retrieve a poster from TMDB and place it under an outbound directory, the implementation does not enforce either constraint. `urllib.request.urlopen()` can access arbitrary network destinations and follows HTTP redirects. There is no validation of the URL scheme, hostname, resolved IP address, redirect destination, response content type, or response size. This can allow server-side request forgery against loopback, private-network, link-local, or cloud metadata services reachable from the Agent host. The output path is also used directly. `Path.write_bytes()` truncates and overwrites an existing file if the process has permission. There is no resolved-path containment check, symlink protection, exclusive file creation, or restriction to the documented outbound directory. The response is read entirely into memory without a size limit and can then consume local disk space. These capabilities exceed the minimum privileges required to download a TMDB poster. ### Attack Path 1. An attacker supplies or causes the Agent to use a crafted asset URL instead of a legitimate TMDB image URL. 2. The Agent i ...[truncated 1173 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/radarr.py:34
Finding

API Credentials Can Be Transmitted over Plaintext HTTP and Exposed in URLs

Content
View full analysis
str: url = _env("RADARR_URL").rstrip("/") return url def _api_key() -> str: return _env("RADARR_API_KEY") def _request(path: str, *, method: str = "GET", params: dict | None = None, body: dict | None = None): base = _base_url() url = base + path if params: qs = urllib.parse.urlencode(params) url = url + ("?" if "?" not in url else "&") + qs headers = { "X-Api-Key": _api_key(), "Accept": "application/json", } data = None if body is not None: data = json.dumps(body).encode("utf-8") headers["Content-Type"] = "application/json" req = urllib.request.Request(url, method=method, headers=headers, data=data) try: with urllib.request.urlopen(req, timeout=30) as resp: ``` Plex similarly accepts an unrestricted base URL and places its token in the query string: ```python def _get_xml(path: str, params: dict | None = None) -> ET.Element | None: base = _plex_base() token = _token() if not base or not token: return None url = base + path q = dict(params or {}) q["X-Plex-Token"] = token url = url + "?" + urllib.parse.urlencode(q) req = urllib.request.Request(url, headers={"Accept": "application/xml"}) with urllib.request.urlopen(req, timeout=30) as resp: raw = resp.read().decode("utf-8", errors="replace") ``` The documentation explicitly encourages plaintext examples: ```text RADARR_URL= ...[truncated 2401 chars]
Remediation
View remediation
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
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
Findings (31)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The skill performs queueing and local state/outbox management for messaging targets, but that operational behavior is not front-and-center in the declared purpose. In practice, undeclared message-queue and tracking behavior can leak chat identifiers, create persistent records, and enable unintended notifications or workflow abuse if users believe they are only invoking a Radarr API wrapper.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The skill performs queueing and local state/outbox management for messaging targets, but that operational behavior is not front-and-center in the declared purpose. In practice, undeclared message-queue and tracking behavior can leak chat identifiers, create persistent records, and enable unintended notifications or workflow abuse if users believe they are only invoking a Radarr API wrapper.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The skill performs queueing and local state/outbox management for messaging targets, but that operational behavior is not front-and-center in the declared purpose. In practice, undeclared message-queue and tracking behavior can leak chat identifiers, create persistent records, and enable unintended notifications or workflow abuse if users believe they are only invoking a Radarr API wrapper.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The skill performs queueing and local state/outbox management for messaging targets, but that operational behavior is not front-and-center in the declared purpose. In practice, undeclared message-queue and tracking behavior can leak chat identifiers, create persistent records, and enable unintended notifications or workflow abuse if users believe they are only invoking a Radarr API wrapper.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The skill performs queueing and local state/outbox management for messaging targets, but that operational behavior is not front-and-center in the declared purpose. In practice, undeclared message-queue and tracking behavior can leak chat identifiers, create persistent records, and enable unintended notifications or workflow abuse if users believe they are only invoking a Radarr API wrapper.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The skill performs queueing and local state/outbox management for messaging targets, but that operational behavior is not front-and-center in the declared purpose. In practice, undeclared message-queue and tracking behavior can leak chat identifiers, create persistent records, and enable unintended notifications or workflow abuse if users believe they are only invoking a Radarr API wrapper.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

The skill performs queueing and local state/outbox management for messaging targets, but that operational behavior is not front-and-center in the declared purpose. In practice, undeclared message-queue and tracking behavior can leak chat identifiers, create persistent records, and enable unintended notifications or workflow abuse if users believe they are only invoking a Radarr API wrapper.

Content

No source excerpt is available for this finding.

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/check_env.py (reported line 39)May include surrounding context.

python
def _env(name: str) -> str:
    v = os.environ.get(name)
    if not v:
        raise SystemExit(f"Missing env var {name}. Set it in ~/.openclaw/.env")
    return v

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/radarr.py (reported line 29)May include surrounding context.

python
def _env(name: str) -> str:
    v = os.environ.get(name)
    if not v:
        raise SystemExit(f"Missing env var {name}. Set it in ~/.openclaw/.env")
    return v

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/radarr_status.py (reported line 34)May include surrounding context.

python
def _env(name: str) -> str:
    v = os.environ.get(name)
    if not v:
        raise SystemExit(f"Missing env var {name}. Set it in ~/.openclaw/.env")
    return v

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/radarr_track.py (reported line 34)May include surrounding context.

python
def _env(name: str) -> str:
    v = os.environ.get(name)
    if not v:
        raise SystemExit(f"Missing env var {name}. Set it in ~/.openclaw/.env")
    return v

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding

The skill declares shell, network, environment, and file read/write capabilities but does not scope or constrain them with an explicit permissions/allowed-tools declaration. In an agent setting, that increases the blast radius of misuse because the skill can fetch remote content, write local files, and invoke scripts without any machine-readable guardrails.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The trigger language is broad enough that the skill may activate on common movie-related requests without clear user intent to perform external actions. In an agent system with shell/network/file access, over-broad activation can cause unintended API calls, file writes, or media requests from casual conversation.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The chat workflow phrase 'request/add ' is ambiguous and can match natural conversation in direct messages or groups, increasing the chance of accidental execution. Because this skill can enqueue tracking and trigger downstream operations, ambiguous activation is more dangerous here than in a read-only informational skill.

Content

No source excerpt is available for this finding.

Autonomous Decision Making

Medium
Category
Excessive Agency
Confidence
85% confidence
Finding

Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Content

Scanner excerpt · references/onboarding.md (reported line 56)May include surrounding context.

PLEX_TOKEN=xxxxxxxx

text

### Optional defaults (skip prompts)
If you set these, requests can be 1‑turn because the bot won’t need to ask which profile/root folder to use.

```bash

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The guide explicitly says the bot posts progress updates and final status in the same chat, including group contexts, but does not clearly warn that movie requests and viewing interests will be visible to all chat participants. This can leak user activity, preferences, and request history to unintended audiences, especially in shared or semi-public group chats.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The manifest describes a skill for interacting with a Radarr instance via its HTTP API to search, list profiles/folders, add movies, and trigger searches. This script instead creates and writes local tracking-job JSON files under workspace/state/radarr/tracks, which is a separate local job-queue/state-management behavior not mentioned in the manifest.

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 · scripts/movie_card.py (reported line 68)May include surrounding context.

python
params = {"api_key": key, "query": title}
    if year:
        params["year"] = str(year)
    url = "https://api.themoviedb.org/3/search/movie?" + urllib.parse.urlencode(params)
    data = _get(url)
    results = data.get("results") or []
    if not results:

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 · scripts/movie_card.py (reported line 80)May include surrounding context.

python
params = {"api_key": key, "query": title}
    if year:
        params["year"] = str(year)
    url = "https://api.themoviedb.org/3/search/movie?" + urllib.parse.urlencode(params)
    data = _get(url)
    results = data.get("results") or []
    if not results:

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 · scripts/movie_card.py (reported line 90)May include surrounding context.

python
params = {"api_key": key, "query": title}
    if year:
        params["year"] = str(year)
    url = "https://api.themoviedb.org/3/search/movie?" + urllib.parse.urlencode(params)
    data = _get(url)
    results = data.get("results") or []
    if not results:

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The script adds undocumented Plex integration to a skill whose stated purpose is Radarr-only movie management. Even though the code is not overtly malicious, this scope expansion causes the skill to access a separate media service and generate links using Plex credentials, increasing the attack surface and violating least surprise for users and reviewers. In agent environments, hidden cross-service access is dangerous because it can enable unintended data access or credential use beyond what the manifest suggests.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The code reads PLEX_URL and PLEX_TOKEN and performs authenticated Plex API requests despite the skill being described as a Radarr automation skill. This creates an undeclared credential boundary crossing: a user invoking Radarr behavior may unknowingly grant the skill access to Plex metadata and library search functions. In a multi-tool or agent setting, undisclosed use of additional secrets is a real security issue because it undermines permission transparency and can expose unrelated systems.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The script expands the skill from Radarr management into a general polling-and-notification pipeline that writes outbound message jobs to an outbox and updates persisted tracking state. That broader behavior increases the skill's authority and data-flow surface beyond the declared purpose, which can enable unintended message delivery, cross-system side effects, and hidden persistence if other components trust these outbox files.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The script conditionally consumes Plex credentials from the environment and invokes Plex link generation even though the skill is described as Radarr-focused. Pulling unrelated service credentials into the execution path broadens secret exposure and creates cross-service reach; if the helper script, workspace, or state files are compromised, Plex access may be abused or metadata exfiltrated.

Content

No source excerpt is available for this finding.

Tainted flow: 'cmd' from os.environ.get (line 77, credential/environment) → subprocess.check_output (code execution)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/poll_and_queue.py (reported line 62)May include surrounding context.

python
def _run_json(cmd: list[str]) -> dict | None:
    try:
        out = subprocess.check_output(cmd, stderr=subprocess.STDOUT, text=True)
    except subprocess.CalledProcessError as e:
        return {"error": True, "output": e.output}

Static analysis

No suspicious patterns detected.