Back to skill

Security audit

Volcengine Doubao Image Gen

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly does what it says, but its video script reads a shared workspace .env file and imports all secrets, while downloads and network permissions are broader than clearly declared.

Review this before installing. It needs your Volcengine ARK_API_KEY and will make external API calls that may incur cost. The main risk is that the video script silently reads a shared workspace .env file and imports all secrets, so avoid using it in workspaces where that file contains unrelated credentials. Also prefer explicit filenames in a controlled output directory and be aware that returned media URLs are downloaded without host allowlisting or size limits.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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
Findings (3)

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
scripts/generate_video.py:28
Finding
Video Script Loads Unrelated Secrets from a Fixed Workspace Environment File<![CDATA[ ## Vulnerability Details **File Location**: `scripts/generate_video.py:28-39,97` **Vulnerability Type**: Excessive access to sensitive configuration **Risk Level**: Medium ### Vulnerable Code ```python def load_env_file(path: str) -> None: env_path = Path(path) if not env_path.exists(): return for line in env_path.read_text(encoding="utf-8", errors="ignore").splitlines(): line = line.strip() if not line or line.startswith("#") or "=" not in line: continue key, value = line.split("=", 1) os.environ.setdefault(key.strip(), value.strip()) ``` The function is automatically invoked against a fixed, privileged workspace path: ```python def main() -> None: load_env_file("/root/.openclaw/workspace/.env") ``` ### Technical Analysis The video-generation script automatically reads `/root/.openclaw/workspace/.env` and imports every key-value pair into its process environment. The declared functionality requires only `ARK_API_KEY`, with optional model configuration through `DOUBAO_VIDEO_MODEL`. Loading all variables from a shared workspace environment file violates least-privilege principles. Unrelated credentials, service tokens, database passwords, or other secrets stored in that file become available to the script and all imported Python components, including the third-party Volcengine SDK. The behavior is not documented in `SKILL.md`, which instructs users to provide `ARK_API_KEY` through the environment. No direct transmission of unrelated environment variables was identified in the reviewed source code, but unnecessarily placing them in the process environment increases their exposure to dependencies, diagnostics, exception handlers, and future code changes. ### Attack Path 1. A user invokes `scripts/generate_video.py`. 2. Before parsing arguments or validating the required credential, the script opens `/root/.openclaw/workspace/.env`. 3. Every syntactically valid entry is inserte ...[truncated 780 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove the automatic loading of `/root/.openclaw/workspace/.env`. 2. Require callers to supply `ARK_API_KEY` through the existing process environment, as documented in `SKILL.md`. 3. If environment-file support is necessary, require an explicit command-line path rather than using a fixed shared path. 4. Parse only an allowlist of required variables, such as `ARK_API_KEY` and `DOUBAO_VIDEO_MODEL`. 5. Do not copy unrelated entries into `os.environ`; retain required configuration in local variables with the shortest practical lifetime. 6. Restrict environment-file permissions and avoid storing credentials for unrelated services in a shared file. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/generate_image.py:107
Finding
Image Downloads Trust Response-Controlled HTTPS Hosts and Read Unbounded Responses<![CDATA[ ## Vulnerability Details **File Location**: `scripts/generate_image.py:107-121,192-204` **Vulnerability Type**: Insufficient outbound URL validation and unbounded download **Risk Level**: Medium ### Vulnerable Code ```python def validate_download_url(url: str) -> str: parsed = urlparse(url) if parsed.scheme != "https": raise ValueError(f"Unsupported image URL scheme: {parsed.scheme or 'empty'}") if not parsed.netloc: raise ValueError("Image URL is missing host") return url def download_image(url: str, filepath: str) -> None: safe_url = validate_download_url(url) safe_path = ensure_safe_output_path(filepath) request = Request(safe_url, headers={"User-Agent": "OpenClaw-DoubaoImageGen/1.0"}) with urlopen(request, timeout=60) as response: content_type = (response.headers.get("Content-Type") or "").lower() if not content_type.startswith("image/"): raise ValueError(f"Downloaded content is not an image: {content_type or 'unknown'}") with open(safe_path, "wb") as f: f.write(response.read()) print(f"✓ Image saved to: {safe_path}") ``` The URL is taken from the remote API response: ```python if "data" in result and len(result["data"]) > 0: for idx, item in enumerate(result["data"]): image_url = item.get("url") if image_url: if len(result["data"]) > 1 and not args.filename: output_file = generate_filename(args.prompt, idx + 1) elif len(result["data"]) > 1 and args.filename: root, ext = os.path.splitext(filename) safe_root = ensure_safe_output_path(root) output_file = f"{safe_root}-{idx + 1}{ext or '.png'}" else: output_file = filename download_image(image_url, output_file) ``` ### Technical Analysis The validation checks only that the URL uses HTTPS and contains a network location. It does not restrict the hos ...[truncated 2094 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Allowlist the exact Volcengine and CDN hostnames documented for generated-image delivery. 2. Resolve and reject loopback, private, link-local, multicast, and otherwise non-public destination addresses where external-only downloads are intended. 3. Disable automatic redirects or validate the scheme, hostname, port, and resolved address after every redirect. 4. Stream the response to disk in bounded chunks instead of calling `response.read()` without a size limit. 5. Enforce a maximum image size using both `Content-Length`, when present, and a running byte counter. 6. Retain the MIME-type check, but do not treat it as a substitute for destination and size validation. 7. Add required CDN hosts to `package.json` network permissions so declared access matches runtime behavior. 8. Delete incomplete output files when validation or download limits fail. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/generate_video.py:66
Finding
Video Downloads Trust Response-Controlled HTTPS Hosts and Read Unbounded Responses<![CDATA[ ## Vulnerability Details **File Location**: `scripts/generate_video.py:66-72,88-95,145-150` **Vulnerability Type**: Insufficient outbound URL validation and unbounded download **Risk Level**: Medium ### Vulnerable Code ```python def validate_download_url(url: str) -> str: parsed = urlparse(url) if parsed.scheme != "https": raise ValueError(f"Unsupported video URL scheme: {parsed.scheme or 'empty'}") if not parsed.netloc: raise ValueError("Video URL is missing host") return url ``` ```python def download_video(url: str, filepath: str) -> None: safe_url = validate_download_url(url) safe_path = ensure_safe_output_path(filepath) request = Request(safe_url, headers={"User-Agent": "OpenClaw-SeedanceVideo/1.0"}) with urlopen(request, timeout=180) as response: content_type = (response.headers.get("Content-Type") or "").lower() if not content_type.startswith("video/"): raise ValueError(f"Downloaded content is not a video: {content_type or 'unknown'}") Path(safe_path).write_bytes(response.read()) print(f"✓ Video saved to: {safe_path}") ``` The download URL is obtained from the remote task response: ```python if current.status == "succeeded": video_url = current.content.video_url if current.content else None if not video_url: print("Error: task succeeded but no video_url returned") sys.exit(1) download_video(video_url, output_file) ``` ### Technical Analysis The video downloader accepts any URL with an HTTPS scheme and a nonempty host. It does not enforce an approved media-host allowlist, validate redirect destinations, reject private or link-local networks, or bound the amount of downloaded data. The value originates in a task-status response obtained through the Volcengine SDK. If that response or upstream service is compromised, the process can be directed to an unintended HTTPS destination. The content-type check is performed only afte ...[truncated 1503 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Restrict generated-video downloads to an explicit allowlist of documented Volcengine media and CDN domains. 2. Validate all redirect targets and reject redirects to unapproved schemes, ports, hosts, or non-public addresses. 3. Stream video content to disk in fixed-size chunks rather than buffering the entire response. 4. Define a maximum permitted video size and abort once the cumulative byte count exceeds it. 5. Check `Content-Length` when available, while retaining a streaming byte limit for chunked or misleading responses. 6. Write to a temporary file in the destination directory and atomically rename it only after successful validation and download. 7. Remove partial files after errors. 8. Update package network permissions to include only the media hosts genuinely required by the service. ]]>
Vulnerability Patterns
  • 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
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (11)

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding
该代码块的主要行为与“豆包图片生成”一致:读取 ARK_API_KEY,向火山引擎的 /api/v3/images/generations 发起请求,接收图片 URL,并校验后下载为本地图片文件。它还支持参考图、多图顺序生成、模型别名和文件名/path 安全处理。这些都属于已声明的图像生成功能范围内。 但声明中明确包含“视频生成”“Seedance 文生视频”,而代码没有任何视频生成专用接口、视频响应处理、视频文件下载或媒体类型处理逻辑。即使模型列表中包含一个 Seedance 模型名,实际请求仍发往图像生成接口,且下载函数强制要求 Content-Type 以 image/ 开头,因此不能认为真正支持视频生成。故存在描述与实际能力不一致。

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The code chunk’s primary function is accurately aligned with the video-generation portion of the description: it uses the Volcengine/Ark API to create a video generation task, waits for completion, and saves an MP4 locally. However, the declared purpose explicitly claims both image and video generation, including Seedream image features, while this code implements only video generation. That is a material description-versus-behavior mismatch for this supplied code chunk. The local file download and environment variable loading are supporting implementation details and not mismatches by themselves.

Credential Access

High
Category
Privilege Escalation
Content
def main() -> None:
    load_env_file("/root/.openclaw/workspace/.env")

    parser = argparse.ArgumentParser(description="豆包视频生成 - 使用火山方舟 Seedance 生成视频")
    parser.add_argument("--prompt", "-p", required=True, help="视频描述/提示词")
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
def main() -> None:
    load_env_file("/root/.openclaw/workspace/.env")

    parser = argparse.ArgumentParser(description="豆包视频生成 - 使用火山方舟 Seedance 生成视频")
    parser.add_argument("--prompt", "-p", required=True, help="视频描述/提示词")
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Lp3

Medium
Category
MCP Least Privilege
Confidence
77% confidence
Finding
The skill documentation describes use of environment variables, local file output, and network access, but it does not declare any tool scope or allowed-tools boundaries. Without explicit permission scoping, an agent runtime may grant broader capabilities than intended, increasing the chance of over-privileged execution or misuse if the skill is invoked unexpectedly.

Vague Triggers

Medium
Confidence
95% confidence
Finding
The trigger list includes broad everyday phrases like '生成图片' and '生成视频', which can cause accidental invocation in unrelated conversations. Because this skill uses network access, API credentials, and file output, false activation can lead to unintended external requests, cost-incurring API usage, and creation of local artifacts without clear user intent.

Scope Creep

Medium
Confidence
98% confidence
Finding
The manifest exposes video generation through the description and the generate:video script, and also defines a video model variable at L38-L42. Yet the only declared outbound permission purpose at L53 says it is for calling the image generation API, so the permission declaration is narrower than the actual stated functionality and does not fully cover video API use.

Intent-Code Divergence

Medium
Confidence
90% confidence
Finding
The module docstring and CLI description state that the script generates images, yet the supported model list includes 'doubao-seedance-1-5-pro-251215', described as a video model. That documentation does not merely omit detail; it conflicts with the code's acceptance of a video-model identifier, creating misleading intent documentation.

Description-Behavior Mismatch

Medium
Confidence
97% confidence
Finding
The manifest describes both image and short-video generation via Seedream and Seedance, but this file's CLI, API endpoint, response handling, and downloader are all specific to images. There is no code path here that generates or saves video output, so the behavior implemented by this script is narrower than the declared skill description.

Description-Behavior Mismatch

Low
Confidence
95% confidence
Finding
The package description at L04 and scripts at L17-L18 clearly indicate both image and video generation capabilities. However, the required secret ARK_API_KEY is documented at L24 as being used only for the image generation API, which mismatches the broader behavior and could mislead operators about what the credential enables.

Natural-Language Policy Violations

Low
Confidence
95% confidence
Finding
The file’s docstring and command-line descriptions are written in Chinese, and the script later continues using Chinese user-facing help text and status messages. This imposes a specific language/locale on users without any opt-in or alternative, which matches the language-policy concern for natural-language policy violations.

Static analysis

No suspicious patterns detected.