Back to skill

Security audit

Openclaw Media Gen

Security checks for vulnerabilities and agentic risk

Overview

The skill does what it claims, but it handles an API key and downloads service-provided media URLs with limited safety controls.

Review before installing. Use AISA_API_KEY from the environment or a credential manager instead of --api-key, run it only in workspaces where generated files can be safely written, and avoid enabling automatic downloads unless you trust the AIsa service response path.

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

Warning
Location
scripts/media_gen_client.py:506
Finding

Command-Line API Key Handling Exposes Credentials to Local Observation and Logging

Content
View full analysis

Vulnerability Details

File Location: SKILL.md:61; scripts/media_gen_client.py:506
Vulnerability Type: Sensitive credential exposure through command-line arguments
Risk Level: Medium

Vulnerable Code

SKILL.md:61:

markdown
- Prefer explicit CLI auth flags when a script exposes them.

scripts/media_gen_client.py:506:

python
p.add_argument("--api-key", help="Override AISA_API_KEY")

The API key is subsequently selected from the explicit command-line value or the environment:

python
def _get_api_key(explicit: Optional[str] = None) -> str:
    api_key = explicit or os.environ.get("AISA_API_KEY")
    if not api_key:
        raise ValueError("AISA_API_KEY is required (env or --api-key).")
    return api_key

Technical Analysis

The client permits the AIsa API key to be supplied as a regular command-line argument, and the Skill documentation explicitly recommends CLI authentication flags. Secrets placed in command-line arguments can be exposed through:

  • Shell history files.
  • Process listings and process-monitoring utilities.
  • CI/CD job metadata and execution logs.
  • Terminal session recording.
  • Diagnostic or endpoint-monitoring telemetry.

Although environment variables are also imperfect, the project already supports AISA_API_KEY, making explicit command-line transmission unnecessary for normal operation. Encouraging the CLI option therefore increases credential exposure beyond the minimum needed for media generation.

The key is legitimately sent as an HTTPS bearer token to the fixed api.aisa.one service. No evidence was found that the application prints the key or sends it to unrelated destinations.

Attack Path

  1. A user follows the documented recommendation and runs a command such as:
    bash
    python3 scripts/media_gen_client.py --api-key SECRET image --prompt "example"
    
  2. The complete command is retained in shell history, captured in an automation log, or temporarily exposed through ...[truncated 620 chars]
Remediation
View remediation

Remediation Suggestions

  1. Remove the instruction in SKILL.md that prefers explicit CLI authentication flags.
  2. Prefer AISA_API_KEY or a platform-provided secure credential store.
  3. Remove --api-key if backward compatibility does not require it.
  4. If the option must remain, document that it is unsafe for shared systems, recorded terminals, and CI/CD environments.
  5. Consider accepting the credential through a non-echoing interactive prompt or standard input where appropriate.
  6. Ensure error messages, diagnostics, and debug logging never include request authorization headers.
  7. Recommend key rotation if a credential has previously been supplied on the command line or retained in logs.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/media_gen_client.py:114
Finding

Unvalidated API-Provided URLs Are Downloaded from the Local Environment

Content
View full analysis

Vulnerability Details

File Location: scripts/media_gen_client.py:114-132, with call sites at scripts/media_gen_client.py:420, 442, and 492
Vulnerability Type: Server-side request forgery-like local fetch behavior and unbounded download
Risk Level: Medium

Vulnerable Code

python
def _download_to_file(url: str, out_path: str, timeout_s: int = 300) -> Dict[str, Any]:
    """
    Download a (possibly signed) URL to local file.
    Designed for OSS signed URLs returned by video generation tasks.
    """
    os.makedirs(os.path.dirname(out_path) or ".", exist_ok=True)
    req = urllib.request.Request(url, headers={"User-Agent": "AIsa-Media-Gen/1.0"})
    try:
        with urllib.request.urlopen(req, timeout=timeout_s) as resp, open(out_path, "wb") as f:
            total = 0
            while True:
                chunk = resp.read(1024 * 1024)  # 1MB
                if not chunk:
                    break
                f.write(chunk)
                total += len(chunk)
        return {"success": True, "saved_to": out_path, "bytes": total}
    except Exception as e:
        return {"success": False, "error": str(e), "url": url, "saved_to": out_path}

API response URLs are passed directly to this downloader:

python
dl = _download_to_file(urls[0], out_path)
python
dl = _download_to_file(url, out_path)
python
dl = _download_to_file(video_url, out_path)

Technical Analysis

Image and video URLs returned by the remote API are passed directly to urllib.request.urlopen without validating:

  • The URL scheme.
  • The destination hostname or resolved IP address.
  • Whether the destination is loopback, link-local, private, or otherwise reserved.
  • Redirect destinations.
  • The response MIME type.
  • The response size.
  • Whether the host belongs to an expected media-storage provider.

Downloading generated media is part of the declared functionality. However, unrestricted fetching provides more network reach than nece ...[truncated 1848 chars]

Remediation
View remediation

Remediation Suggestions

  1. Accept only https download URLs.
  2. Reject URLs containing embedded credentials or unexpected ports.
  3. Resolve the destination and reject loopback, link-local, private, multicast, unspecified, and reserved IP ranges.
  4. Repeat destination validation after every redirect, or disable automatic redirects and process them manually.
  5. Prefer an allowlist of documented AIsa media-storage domains where operationally possible.
  6. Guard against DNS rebinding by validating and constraining the address actually used for the connection.
  7. Set a strict maximum download size and stop before writing beyond that limit.
  8. Validate Content-Type against expected image or video formats.
  9. Download to a temporary file with restrictive permissions and atomically rename it only after successful validation.
  10. Remove partial files after download failures or limit violations.
  11. Consider checking media signatures or magic bytes before treating the output as a supported image or video.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (13)

Tainted flow: 'req' from os.environ.get (line 94, credential/environment) → urllib.request.urlopen (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 · scripts/media_gen_client.py (reported line 96)May include surrounding context.

python
req = urllib.request.Request(url, data=data, headers=all_headers, method=method.upper())
    try:
        with urllib.request.urlopen(req, timeout=timeout_s) as resp:
            raw = resp.read().decode("utf-8")
            return json.loads(raw) if raw else {}
    except urllib.error.HTTPError as e:

Tainted flow: 'req' from os.environ.get (line 94, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

The helper downloads arbitrary URLs returned by upstream APIs or supplied indirectly through task status responses without validating scheme, host, or content size. If an attacker can influence those URLs, this can trigger SSRF-like behavior from the agent host or cause retrieval of unexpected internal/local resources and large unbounded downloads.

Content

Scanner excerpt · scripts/media_gen_client.py (reported line 122)May include surrounding context.

python
os.makedirs(os.path.dirname(out_path) or ".", exist_ok=True)
    req = urllib.request.Request(url, headers={"User-Agent": "AIsa-Media-Gen/1.0"})
    try:
        with urllib.request.urlopen(req, timeout=timeout_s) as resp, open(out_path, "wb") as f:
            total = 0
            while True:
                chunk = resp.read(1024 * 1024)  # 1MB

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding

The skill requires an API key in environment variables and is designed to invoke a Python client against an external service, but it does not declare any explicit tool scope such as permissions or allowed-tools. That creates an authorization gap: an agent runtime may permit broader env or network access than intended, increasing the risk of secret exposure or unintended outbound requests if the implementation changes or is misused.

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/media_gen_client.py (reported line 8)May include surrounding context.

python
Image generation (3 endpoint paths, routed by model):

  gemini-3-pro-image-preview
      POST https://api.aisa.one/v1/models/{model}:generateContent
      Gemini GenerateContent; images arrive as base64 in candidates[].parts[].inline_data.

  wan2.7-image, wan2.7-image-pro

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/media_gen_client.py (reported line 12)May include surrounding context.

python
Image generation (3 endpoint paths, routed by model):

  gemini-3-pro-image-preview
      POST https://api.aisa.one/v1/models/{model}:generateContent
      Gemini GenerateContent; images arrive as base64 in candidates[].parts[].inline_data.

  wan2.7-image, wan2.7-image-pro

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/media_gen_client.py (reported line 17)May include surrounding context.

python
Image generation (3 endpoint paths, routed by model):

  gemini-3-pro-image-preview
      POST https://api.aisa.one/v1/models/{model}:generateContent
      Gemini GenerateContent; images arrive as base64 in candidates[].parts[].inline_data.

  wan2.7-image, wan2.7-image-pro

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/media_gen_client.py (reported line 23)May include surrounding context.

python
Image generation (3 endpoint paths, routed by model):

  gemini-3-pro-image-preview
      POST https://api.aisa.one/v1/models/{model}:generateContent
      Gemini GenerateContent; images arrive as base64 in candidates[].parts[].inline_data.

  wan2.7-image, wan2.7-image-pro

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/media_gen_client.py (reported line 24)May include surrounding context.

python
Image generation (3 endpoint paths, routed by model):

  gemini-3-pro-image-preview
      POST https://api.aisa.one/v1/models/{model}:generateContent
      Gemini GenerateContent; images arrive as base64 in candidates[].parts[].inline_data.

  wan2.7-image, wan2.7-image-pro

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/media_gen_client.py (reported line 46)May include surrounding context.

python
Image generation (3 endpoint paths, routed by model):

  gemini-3-pro-image-preview
      POST https://api.aisa.one/v1/models/{model}:generateContent
      Gemini GenerateContent; images arrive as base64 in candidates[].parts[].inline_data.

  wan2.7-image, wan2.7-image-pro

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/media_gen_client.py (reported line 47)May include surrounding context.

python
Image generation (3 endpoint paths, routed by model):

  gemini-3-pro-image-preview
      POST https://api.aisa.one/v1/models/{model}:generateContent
      Gemini GenerateContent; images arrive as base64 in candidates[].parts[].inline_data.

  wan2.7-image, wan2.7-image-pro

Tainted flow: 'out_path' from os.environ.get (line 406, credential/environment) → open (file write)

Medium
Category
Data Flow
Confidence
86% confidence
Finding

The function writes downloaded content to a caller-controlled output path with no path validation or sandboxing. In an agent context, a user can potentially overwrite arbitrary writable files, including sensitive workspace files, configuration, or other artifacts, especially when combined with automatic downloads from remote URLs.

Content

Scanner excerpt · scripts/media_gen_client.py (reported line 122)May include surrounding context.

python
os.makedirs(os.path.dirname(out_path) or ".", exist_ok=True)
    req = urllib.request.Request(url, headers={"User-Agent": "AIsa-Media-Gen/1.0"})
    try:
        with urllib.request.urlopen(req, timeout=timeout_s) as resp, open(out_path, "wb") as f:
            total = 0
            while True:
                chunk = resp.read(1024 * 1024)  # 1MB

Tainted flow: 'out_path' from os.environ.get (line 406, credential/environment) → open (file write)

Medium
Category
Data Flow
Confidence
88% confidence
Finding

Image bytes returned by the remote service are written directly to a user-specified path without restrictions. In a skill/agent environment, unrestricted file write enables clobbering arbitrary files the process can access, which is more dangerous than a normal desktop CLI because the tool may run with access to shared workspace state.

Content

Scanner excerpt · scripts/media_gen_client.py (reported line 407)May include surrounding context.

python
return 1
        mime, data = images[0]
        out_path = args.out or _safe_filename(_ext_from_mime(mime))
        with open(out_path, "wb") as f:
            f.write(data)
        _print_json({"success": True, "route": route, "model": args.model, "mime_type": mime,
                     "saved_to": out_path, "images_returned": len(images)})

Tainted flow: 'out_path' from os.environ.get (line 406, credential/environment) → open (file write)

Medium
Category
Data Flow
Confidence
88% confidence
Finding

Base64-decoded image content from the remote API is saved to a caller-controlled path with no path safety checks. This creates the same arbitrary file overwrite risk as the other write sites and could be abused to alter files used by the agent or user workflow.

Content

Scanner excerpt · scripts/media_gen_client.py (reported line 435)May include surrounding context.

python
kind, data, url = images[0]
        out_path = args.out or _safe_filename("png")
        if kind == "b64" and data is not None:
            with open(out_path, "wb") as f:
                f.write(data)
            _print_json({"success": True, "route": route, "model": args.model,
                         "saved_to": out_path, "images_returned": len(images),

Static analysis

No suspicious patterns detected.