Back to skill

Security audit

Media Generation

Security checks for vulnerabilities and agentic risk

Overview

The skill is coherent for media generation, but its video polling and media download helpers can make unsafe provider-directed network requests that may expose API keys or hit unintended hosts.

Review or patch this skill before installing. Use only trusted media providers, avoid sensitive prompts or private images unless the provider is approved, run it with restricted network access where possible, and fix URL validation so polling and downloads only contact trusted HTTPS provider or CDN origins without leaking bearer tokens. Clean tmp/images, tmp/videos, masks, and batch summaries after sensitive jobs.

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/generate_video.py:138
Finding
Provider-Controlled Video Polling URL Leaks the Provider Bearer Token## Vulnerability Details **File Location**: `scripts/generate_video.py`, lines 138–152 **Vulnerability Type**: Arbitrary authenticated outbound request / credential disclosure **Risk Level**: High ### Vulnerable Code ```python status_url = extract_status_url(payload) job_id = extract_job_id(payload) if not status_url and job_id: status_url = base_url + endpoint_template.format(id=job_id) if not status_url: return payload if status_url.startswith("/"): status_url = base_url + status_url headers = {"Authorization": f"Bearer {api_key}", "Accept": "application/json"} last_payload = payload for _ in range(max_polls): try: resp = requests.get(status_url, headers=headers, timeout=timeout) ``` ### Technical Analysis The asynchronous video workflow accepts a polling URL obtained from the provider response through `extract_status_url()`. Absolute URLs are used without validating their scheme, hostname, port, resolved IP address, or relationship to the configured provider origin. The request then unconditionally includes the configured provider API key in an `Authorization: Bearer` header. A malicious or compromised generation endpoint can return an attacker-controlled absolute `status_url`, `poll_url`, `result_url`, or `retrieve_url`. The polling routine will consequently send the provider credential to that destination. This behavior exceeds the minimum privileges required for asynchronous polling. Poll requests only need to reach the configured provider or an explicitly trusted polling origin; they do not need arbitrary Internet or internal-network access with provider credentials attached. ### Attack Path 1. An attacker controls, compromises, or impersonates the configured video-generation endpoint. 2. A user invokes `generate_video.py`, causing an authenticated generation request. 3. The endpoint returns a successful asynchronous payload containing an absolute URL such as an attacker ...[truncated 1211 chars]
Remediation
## Remediation Suggestions 1. Resolve relative polling paths against the configured provider URL using a standards-compliant URL resolver such as `urllib.parse.urljoin`. 2. Permit polling only when the resulting URL has the same normalized scheme, hostname, and effective port as the configured provider. 3. Require HTTPS except where an explicit, narrowly scoped local-development option permits HTTP. 4. Never attach the provider `Authorization` header to a cross-origin polling request. 5. Reject URLs resolving to loopback, private, link-local, multicast, reserved, or unspecified IP ranges unless explicitly required by trusted local configuration. 6. Resolve and validate all returned addresses to mitigate DNS rebinding. 7. Disable redirects for authenticated polling, or manually validate every redirect target before following it. 8. Add tests covering attacker-controlled absolute polling URLs, cross-origin redirects, IPv4 and IPv6 loopback addresses, private ranges, and cloud metadata addresses.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/fetch_generated_media.py:91
Finding
Unrestricted Returned-Media Fetching Enables Server-Side Request Forgery## Vulnerability Details **File Location**: `scripts/fetch_generated_media.py`, lines 91–99 and 161–165 **Vulnerability Type**: Server-side request forgery and unbounded response download **Risk Level**: Medium ### Vulnerable Code ```python def build_url(ref, origin): if ref.startswith('http://') or ref.startswith('https://'): return ref.rstrip('\\') if ref.startswith('data:'): return ref if not origin: raise SystemExit(f"relative media path found but --origin was not provided: {ref}") return origin.rstrip('/') + '/' + ref.lstrip('/') ``` ```python headers = parse_headers(args.header) resp = requests.get(url, headers=headers, timeout=args.timeout) resp.raise_for_status() ext = guess_extension(url, resp.headers.get('content-type')) out_path = save_bytes(args.out_dir, args.prefix, ext, resp.content) ``` ### Technical Analysis The media-fetch helper accepts absolute HTTP and HTTPS references extracted from provider-controlled JSON, HTML, Markdown, or raw text. It performs the request without validating the destination hostname, resolved IP address, port, or network range. Redirects are followed by default by the `requests` library, and redirect destinations are not revalidated. A public URL can therefore redirect the downloader to a loopback, private, link-local, or cloud metadata address. The implementation also accesses `resp.content`, buffering the entire response in memory before writing it to disk. It imposes no maximum response size and does not require an image or video content type. A malicious endpoint can consequently return an oversized or non-media response, causing memory and disk consumption. Fetching generated media is necessary for the declared functionality, but unrestricted access to arbitrary network destinations and unlimited response bodies is not. ### Attack Path 1. A malicious or compromised media provider returns a response containing a ...[truncated 1578 chars]
Remediation
## Remediation Suggestions 1. Allow downloads only from the configured provider origin and explicitly approved media CDN origins. 2. Require HTTPS for remote media outside narrowly scoped local-development configurations. 3. Resolve destination hostnames and reject loopback, private, link-local, multicast, unspecified, and reserved IPv4 and IPv6 ranges. 4. Protect against DNS rebinding by validating every resolved address and ensuring the actual connection cannot switch to an unapproved address. 5. Disable automatic redirects or validate the scheme, origin, hostname, port, and resolved addresses of every redirect target. 6. Use `stream=True` and process the body in bounded chunks. 7. Enforce a strict maximum download size using both `Content-Length` when available and a running byte counter. 8. Permit only expected image and video MIME types, and verify file signatures rather than relying solely on response headers or filename extensions. 9. Apply separate connection and read timeouts. 10. Add tests for loopback and private addresses, IPv6 literals, encoded IP forms, DNS rebinding, redirect chains, misleading content types, and oversized responses.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Rogue AgentSelf-Modification, Session Persistence
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (21)

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill documents capabilities that require shell, file, environment, and network access, but it does not declare any explicit tool scope such as permissions or allowed-tools. That creates an unnecessarily broad execution surface and weakens policy enforcement, making it easier for the skill to invoke powerful operations without clear restriction or review.

Session Persistence

Medium
Category
Rogue Agent
Content
---
name: media-generation
description: Generate images, edit existing images, create short videos, run inpainting/outpainting and object-focused edits, use reference images as provider inputs, batch related media jobs from a manifest, and fetch returned media from URLs/HTML/JSON/data URLs/base64. Use when working on AI image generation, AI image editing, mask-based inpainting, outpainting, reference-image workflows, short AI video generation, product-shot variations, or reusable media-production pipelines.
---

# Media Generation
Confidence
72% confidence
Finding
The skill is designed to save generated and fetched media under tmp/images and tmp/videos and to support batch workflows and reusable pipelines, which introduces session persistence of user-provided and provider-returned artifacts. Persisting prompts, reference-derived outputs, and fetched remote content beyond the immediate task can increase privacy exposure and create residual sensitive data on disk.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The skill explicitly supports sending prompts and reference images to external providers and fetching returned media from remote URLs, HTML, JSON, data URLs, and base64, but the description does not clearly warn users that their content may leave the local environment. This can lead to unintentional disclosure of sensitive prompts, images, or internal URLs and causes users to consent to behavior they may not understand.

Missing User Warnings

Medium
Confidence
87% confidence
Finding
The documented batch summary persists rendered items, resolved commands, and per-item stdout/stderr, which can expose sensitive prompts, local file paths, provider endpoints, tokens passed via arguments, or other operational details if users store or share the summary artifact. In a media-generation workflow, manifests often contain proprietary prompts, asset locations, and integration parameters, so documenting this behavior without an explicit warning or redaction guidance increases the chance of unintentional data leakage.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The documentation explicitly states that reference images are read from local paths and uploaded or encoded for provider requests, but it does not warn users that these files may contain sensitive or proprietary content that will be transmitted to third-party services. In a media-generation skill, this omission increases the risk of unintentional privacy, confidentiality, or compliance violations because users may reasonably provide internal or personal images without realizing they leave the local environment.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
This script uploads the provided image and optional mask to a remote provider API via multipart HTTP POST, but it does not present any explicit user-facing notice or confirmation that local media will leave the system. In a media-generation skill, users may reasonably expect cloud processing, but the absence of a clear warning still creates a privacy and data-handling risk, especially if sensitive images are edited unintentionally.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The script performs an HTTP GET to a model-extracted URL and then writes the downloaded content to disk. While these actions are core to the utility, the code provides no confirmation prompt, warning message, or explanatory comment/docstring disclosing that it will contact a remote host and save arbitrary media locally.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
if __name__ == "__main__":
    target = Path(__file__).resolve().parent / "reference_media.py"
    raise SystemExit(subprocess.call([sys.executable, str(target), *sys.argv[1:]]))
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

External Transmission

Medium
Category
Data Exfiltration
Content
if args.model == "image-model":
        print("WARN: using placeholder model 'image-model'. Pass --model or set the OPENCLAW_MEDIA_IMAGE_MODEL env var for a real provider model name.", file=sys.stderr)
    try:
        resp = requests.post(
            url,
            headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"},
            json=payload,
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
timeout=args.timeout,
                )
        else:
            resp = requests.post(
                url,
                headers={**headers, "Content-Type": "application/json"},
                json=payload,
Confidence
80% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
cmd.extend(["--origin", origin])
    for header in headers or []:
        cmd.extend(["--header", header])
    result = subprocess.run(cmd, capture_output=True, text=True, check=True)
    lines = [line for line in result.stdout.strip().splitlines() if line.strip()]
    return lines[-1] if lines else None
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def run_cmd(cmd):
    return subprocess.run(cmd, text=True, capture_output=True)


def main():
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def run_attempt(cmd, script_name, label):
    proc = subprocess.run(cmd, text=True, capture_output=True)
    return {
        "label": label,
        "script": script_name,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def run_attempt(cmd, script_name, label):
    proc = subprocess.run(cmd, text=True, capture_output=True)
    return {
        "label": label,
        "script": script_name,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def run(cmd):
    result = subprocess.run(cmd, text=True, capture_output=True)
    return {"cmd": cmd, "returncode": result.returncode, "stdout": result.stdout.strip(), "stderr": result.stderr.strip()}
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def run(cmd):
    result = subprocess.run(cmd, text=True, capture_output=True)
    return {"cmd": cmd, "returncode": result.returncode, "stdout": result.stdout.strip(), "stderr": result.stderr.strip()}
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def run(cmd):
    result = subprocess.run(cmd, text=True, capture_output=True)
    return {"cmd": cmd, "returncode": result.returncode, "stdout": result.stdout.strip(), "stderr": result.stderr.strip()}
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Missing User Warnings

Low
Confidence
81% confidence
Finding
This code invokes another Python script via subprocess.run, which is a safety-relevant operation under the audit criteria for code files. While the helper name suggests expected behavior, there is no confirmation prompt, user-facing log/print, or explanatory comment/docstring in this file disclosing that an external process will be launched.

Missing User Warnings

Low
Confidence
80% confidence
Finding
This code creates directories and output mask files, then invokes other scripts via subprocess to prepare masks and edit images. While these actions are part of the script's purpose, there is no explicit user-facing disclosure at execution time that files will be written and external helper scripts will be run.

Missing User Warnings

Low
Confidence
87% confidence
Finding
The script creates the output directory and saves a prepared image file to disk, but there is no confirmation prompt, pre-write notice, or inline comment/docstring warning that a new file will be written. Although the script prints the prepared path later, that disclosure happens only after the write has already occurred.

Missing User Warnings

Low
Confidence
86% confidence
Finding
The script constructs and executes a subprocess to run another helper, which is a safety-relevant operation under this rule. While the argument parser description says it will call the image-edit helper, there is no explicit runtime notice or warning to the user before launching the subprocess.

Static analysis

Detected: suspicious.dynamic_code_execution

Dynamic code execution detected.

Critical
Code
suspicious.dynamic_code_execution
Location
scripts/mask_inpaint.py:152