Back to skill

Security audit

AIML Generate images and videos

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches its media-generation purpose, but its scripts trust remote API responses enough to create avoidable network and file-write risks.

Review before installing. Use this only in environments where AIMLAPI may receive your prompts and any local image files you explicitly pass. Avoid sensitive prompts or private files. Run it with a constrained output directory and network environment until media URL validation, bounded downloads, and safe video filename generation are added.

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/gen_image.py:105
Finding

Unvalidated Server-Provided Media URLs Permit Server-Side Request Forgery

Content
View full analysis

Vulnerability Details

File Location: scripts/gen_image.py:105-110; scripts/gen_video.py:134-139, 172-179
Vulnerability Type: Unvalidated remote URL retrieval / SSRF
Risk Level: Medium

Vulnerable Code

python
# scripts/gen_image.py:105-110
for index, item in enumerate(data, start=1):
    url_value = item.get("url")
    file_path = out_dir / f"image-{int(time.time())}-{index}.png"
    req = urllib.request.Request(url_value, headers={"User-Agent": args.user_agent})
    with urllib.request.urlopen(req) as res:
        file_path.write_bytes(res.read())
python
# scripts/gen_video.py:134-139
def download_video(url: str, path: pathlib.Path, timeout: int, user_agent: str, verbose: bool) -> None:
    req = urllib.request.Request(url, headers={"User-Agent": user_agent}, method="GET")
    if verbose:
        print(f"[debug] GET {url} -> {path}")
    with urllib.request.urlopen(req, timeout=timeout) as response:
        path.write_bytes(response.read())
python
# scripts/gen_video.py:172-179
video_url = (final.get("video") or {}).get("url")
if not video_url:
    raise SystemExit(f"Completed response does not contain video url: {json.dumps(final)}")

out_dir = ensure_dir(args.out_dir)
extension = pathlib.Path(video_url).suffix or ".mp4"
output_path = out_dir / f"video-{generation_id.split(':', 1)[0]}{extension}"
download_video(video_url, output_path, args.timeout, args.user_agent, args.verbose)

Technical Analysis

Both scripts trust media URLs returned by AIMLAPI and pass them directly to urllib.request.urlopen. The code does not validate the URL scheme, destination hostname, resolved IP address, port, or redirect chain.

Consequently, a malicious or compromised API response could direct the scripts to request loopback, link-local, private-network, cloud metadata, or other unintended resources. Redirects create the same risk even when the initial URL appe ...[truncated 1742 chars]

Remediation
View remediation

Remediation Suggestions

  1. Accept only https media URLs.
  2. Maintain an explicit allowlist of AIMLAPI-approved media download hostnames.
  3. Resolve destination hostnames before connecting and reject loopback, private, link-local, multicast, reserved, and unspecified IP ranges for both IPv4 and IPv6.
  4. Disable automatic redirects or validate the scheme, hostname, port, and resolved address at every redirect hop.
  5. Reject URLs containing credentials and restrict destination ports to approved HTTPS ports.
  6. Apply a maximum response size and stream downloads in bounded chunks instead of calling response.read() without a limit.
  7. Apply the same validation helper consistently to both image and video downloads.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/gen_video.py:146
Finding

Unsanitized Generation ID Can Influence the Video Output Path

Content
View full analysis

Vulnerability Details

File Location: scripts/gen_video.py:146-150, 172-179
Vulnerability Type: Path traversal / unsafe filename construction
Risk Level: Medium

Vulnerable Code

python
# scripts/gen_video.py:146-150
create_response = create_generation(args, api_key)
generation_id = create_response.get("id")
if not generation_id:
    raise SystemExit(f"Video API did not return generation id: {json.dumps(create_response)}")
print(f"Generation ID: {generation_id}")
python
# scripts/gen_video.py:172-179
video_url = (final.get("video") or {}).get("url")
if not video_url:
    raise SystemExit(f"Completed response does not contain video url: {json.dumps(final)}")

out_dir = ensure_dir(args.out_dir)
extension = pathlib.Path(video_url).suffix or ".mp4"
output_path = out_dir / f"video-{generation_id.split(':', 1)[0]}{extension}"
download_video(video_url, output_path, args.timeout, args.user_agent, args.verbose)

Technical Analysis

The output filename contains a generation ID obtained from the remote API. The value is not restricted to safe filename characters and may contain path separators or traversal components. The media URL also controls the selected extension without an allowlist.

Prefixing the value with video- prevents a directly supplied absolute path, but it does not reliably eliminate traversal through embedded separators and .. components. Successful traversal may depend on the crafted path's intermediate directories already existing, because the script creates only the configured output directory.

If a crafted path resolves to an existing writable file outside the output directory, path.write_bytes() truncates and overwrites that file.

Attack Path

  1. An attacker compromises or controls the video-generation API response.
  2. The create response contains a crafted generation ID with path separators and traversal components.
  3. The polling response r ...[truncated 997 chars]
Remediation
View remediation

Remediation Suggestions

  1. Do not derive local filenames from remote generation IDs. Generate a local random filename using uuid.uuid4() or a securely created temporary file.
  2. If the generation ID must be retained, replace all characters outside a strict allowlist such as [A-Za-z0-9_-] and enforce a short maximum length.
  3. Use a fixed media extension such as .mp4, or map validated response content types to a small extension allowlist.
  4. Resolve both the output directory and candidate path with Path.resolve(), then verify that the candidate remains beneath the resolved output directory before writing.
  5. Open new files using exclusive creation where overwriting is unnecessary.
  6. Store the remote generation ID as metadata rather than incorporating it directly into a filesystem path.
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
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (13)

Tainted flow: 'req' from os.getenv (line 64, 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/gen_image.py (reported line 74)May include surrounding context.

python
while True:
        try:
            if verbose: print(f"[debug] POST {url} attempt {attempt + 1}")
            with urllib.request.urlopen(req, timeout=timeout) as response:
                return json.loads(response.read().decode("utf-8"))
        except (urllib.error.HTTPError, urllib.error.URLError) as exc:
            if attempt < retry_max:

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

Critical
Category
Data Flow
Confidence
95% confidence
Finding

The script downloads a URL taken directly from the remote API response without validating the scheme, host, or content type. If the upstream service is compromised, malicious, or returns attacker-controlled URLs, this creates an SSRF-style arbitrary fetch primitive that could access internal resources or unexpected local/file URLs depending on runtime support.

Content

Scanner excerpt · scripts/gen_image.py (reported line 109)May include surrounding context.

python
url_value = item.get("url")
        file_path = out_dir / f"image-{int(time.time())}-{index}.png"
        req = urllib.request.Request(url_value, headers={"User-Agent": args.user_agent})
        with urllib.request.urlopen(req) as res:
            file_path.write_bytes(res.read())
    print(f"SUCCESS: {out_dir}")

Tainted flow: 'req' from os.getenv (line 113, 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/gen_video.py (reported line 70)May include surrounding context.

python
attempt = 0
    while True:
        try:
            with urllib.request.urlopen(req, timeout=timeout) as response:
                return json.loads(response.read().decode("utf-8"))
        except urllib.error.HTTPError as exc:
            detail = exc.read().decode("utf-8") if exc.fp else str(exc)

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

Critical
Category
Data Flow
Confidence
94% confidence
Finding

The script downloads a URL returned by the external API and passes it directly to urllib.request.urlopen without validating the scheme, host, or destination. If the upstream service is compromised or returns an attacker-controlled URL, this can trigger server-side request forgery or access to internal/local resources, and the downloaded content is then written to disk.

Content

Scanner excerpt · scripts/gen_video.py (reported line 138)May include surrounding context.

python
req = urllib.request.Request(url, headers={"User-Agent": user_agent}, method="GET")
    if verbose:
        print(f"[debug] GET {url} -> {path}")
    with urllib.request.urlopen(req, timeout=timeout) as response:
        path.write_bytes(response.read())

Tp4

High
Category
MCP Tool Poisoning
Confidence
93% confidence
Finding

The skill description and the described behavior are inconsistent, especially around video generation, async polling, and filesystem writes. When a skill’s declared purpose does not accurately reflect its operational behavior, reviewers and users can make unsafe assumptions about what it will do, which can hide side effects such as local file creation or unexpected network activity.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding

The skill declares access to an API key and describes network and local file output behavior, but it does not define any explicit tool scope such as permissions or allowed-tools. That creates unnecessary ambiguity about what the agent may access or modify at runtime, increasing the risk of overbroad file, network, or environment use beyond the intended AIMLAPI media-generation workflow.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

This code builds and sends a POST request to an external image-generation service containing the user's prompt and, when --image-url points to a local file, a base64-encoded copy of that file. Aside from the argument parser description, there is no explicit warning, confirmation, or user-facing disclosure that local file contents and prompt data will be uploaded to a third-party service.

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 · references/aimlapi-media.md (reported line 5)May include surrounding context.

md
import urllib.request
from typing import Any

DEFAULT_BASE_URL = "https://api.aimlapi.com/v2"
DEFAULT_USER_AGENT = "openclaw-skill-aimlapi-media-gen/1.1"
ACTIVE_STATUSES = {"waiting", "active", "queued", "generating", "processing"}
SUCCESS_STATUSES = {"completed", "succeeded", "done"}

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 · references/aimlapi-media.md (reported line 6)May include surrounding context.

md
import urllib.request
from typing import Any

DEFAULT_BASE_URL = "https://api.aimlapi.com/v2"
DEFAULT_USER_AGENT = "openclaw-skill-aimlapi-media-gen/1.1"
ACTIVE_STATUSES = {"waiting", "active", "queued", "generating", "processing"}
SUCCESS_STATUSES = {"completed", "succeeded", "done"}

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

python
import urllib.request
from typing import Any

DEFAULT_BASE_URL = "https://api.aimlapi.com/v2"
DEFAULT_USER_AGENT = "openclaw-skill-aimlapi-media-gen/1.1"
ACTIVE_STATUSES = {"waiting", "active", "queued", "generating", "processing"}
SUCCESS_STATUSES = {"completed", "succeeded", "done"}

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

python
import urllib.request
from typing import Any

DEFAULT_BASE_URL = "https://api.aimlapi.com/v2"
DEFAULT_USER_AGENT = "openclaw-skill-aimlapi-media-gen/1.1"
ACTIVE_STATUSES = {"waiting", "active", "queued", "generating", "processing"}
SUCCESS_STATUSES = {"completed", "succeeded", "done"}

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
84% confidence
Finding

This function accesses a sensitive credential via AIMLAPI_API_KEY or an API key file, but the file contains no warning or explanatory comment about secret handling. Under this rule, access to credentials should have some visible disclosure unless already documented elsewhere.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Low
Category
Not specified by scanner
Confidence
76% confidence
Finding

The manifest and CLI description focus on image generation via AIMLAPI, but after generation the code issues a second network request to whatever URL appears in the API response and downloads it. Downloading externally hosted result URLs may be a practical implementation detail, but it is broader than the narrowly described '/images/generations' request behavior.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.