Back to skill

Security audit

doubao-seedance-skill

Security checks for vulnerabilities and agentic risk

Overview

This skill coherently calls the VolcEngine Seedance video API and saves generated videos locally, with ordinary integration risks users should understand before use.

Install only if you intend to use VolcEngine Seedance and are comfortable sending prompts and image URLs to that service. Keep VOLCENGINE_API_KEY in environment configuration, avoid sensitive prompts or private image URLs unless permitted by your data policy, and consider adding request timeouts, bounded streaming, and video URL validation before using it in shared or production environments.

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 (1)

T09 · Insecure Skill Coding Practices

Warning
Location
seedance-api.py:120
Finding
Unbounded and Timeout-Free HTTP Requests Enable Resource Exhaustion<![CDATA[ ## Vulnerability Details **File Location**: `seedance-api.py`, lines 120–148 **Vulnerability Type**: Missing network timeouts and unbounded response buffering **Risk Level**: Medium ### Vulnerable Code ```python response = requests.post(API_URL, headers=headers, json=data) result = response.json() ``` ```python while time.time() - start_time < timeout: query_response = requests.get(f"{QUERY_URL}/{task_id}", headers=headers) query_result = query_response.json() ``` ```python if download and video_url: os.makedirs(output_dir, exist_ok=True) filename = f"{prompt[:20]}_{int(time.time())}.mp4" filename = "".join(c for c in filename if c not in r'<>:"/\|?*') filepath = os.path.join(output_dir, filename) print(f"正在下载视频...") video_response = requests.get(video_url) with open(filepath, "wb") as f: f.write(video_response.content) ``` ### Technical Analysis All three HTTP operations are performed without explicit connection or read timeouts. Consequently, the initial task submission, an individual polling request, or the final video download can wait indefinitely if the remote endpoint accepts a connection but does not complete its response. The application-level polling limit does not fully mitigate this issue. The following condition is evaluated only before each polling request: ```python while time.time() - start_time < timeout: ``` If `requests.get()` blocks during a polling request, execution cannot return to the loop condition to enforce the configured timeout. The video response is also accessed through `video_response.content`, which buffers the complete response body in memory before writing it to disk. No maximum response size, content type, HTTP status, or available disk-space validation is applied. Redirects are followed by default. This combination permits a slow or oversized response to consume execution time, memory, and disk resources. The issue does not provide direct privilege escalatio ...[truncated 1690 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Set explicit connection and read timeouts on every request: ```python REQUEST_TIMEOUT = (10, 60) response = requests.post( API_URL, headers=headers, json=data, timeout=REQUEST_TIMEOUT, ) response.raise_for_status() ``` Apply equivalent timeouts and status checks to polling requests. 2. Enforce the overall polling deadline when calculating individual request timeouts so that one request cannot exceed the remaining task deadline. 3. Stream video downloads rather than buffering the entire body: ```python MAX_VIDEO_BYTES = 500 * 1024 * 1024 with requests.get( video_url, stream=True, timeout=(10, 60), ) as video_response: video_response.raise_for_status() content_type = video_response.headers.get("Content-Type", "") if not content_type.lower().startswith("video/"): raise ValueError("Unexpected video response content type") declared_size = video_response.headers.get("Content-Length") if declared_size and int(declared_size) > MAX_VIDEO_BYTES: raise ValueError("Video exceeds the permitted download size") downloaded = 0 with open(filepath, "xb") as output: for chunk in video_response.iter_content(chunk_size=1024 * 1024): if not chunk: continue downloaded += len(chunk) if downloaded > MAX_VIDEO_BYTES: raise ValueError("Video exceeds the permitted download size") output.write(chunk) ``` 4. Remove partial output files when a timeout, validation failure, or download error occurs. 5. Validate the returned URL before downloading it. Require HTTPS and, where compatible with the service contract, allowlist expected CDN hostnames. Revalidate the destination after redirects or disable redirects and process them explicitly. 6. Check available disk capacity before downloading and enforce per-file and cumulative output quotas. 7. Catch `requests.Timeout`, `requests.ConnectionE ...[truncated 110 chars]
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 (10)

Tainted flow: 'headers' from os.getenv (line 72, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Content
data["seed"] = seed
    
    print(f"正在创建视频生成任务...")
    response = requests.post(API_URL, headers=headers, json=data)
    result = response.json()
    
    if "id" not in result:
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'headers' from os.getenv (line 72, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Content
start_time = time.time()
    while time.time() - start_time < timeout:
        query_response = requests.get(f"{QUERY_URL}/{task_id}", headers=headers)
        query_result = query_response.json()
        
        status = query_result.get("status", "unknown")
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Lp3

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding
The skill declares access to environment variables and performs networked API calls, but does not specify any tool scope such as permissions or allowed-tools. That omission weakens least-privilege boundaries and makes the skill's operational reach less transparent to the host system and user, increasing the chance of unintended secret access or external communication.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The skill states it will directly call an external API and return a local file, but it does not warn that user prompts and optional image URLs will be sent to a third-party service or that files will be written locally. This can cause users to unknowingly disclose sensitive data externally or create artifacts on disk in environments where storage and data handling require explicit consent.

Natural-Language Policy Violations

Medium
Confidence
94% confidence
Finding
This Python file contains user-facing descriptions, CLI help text, and runtime print messages entirely in Chinese, such as the module description and argument help strings. Because the skill does not provide any language/locale opt-in or explain that it is intentionally limited to Chinese users, it violates the language-choice policy for natural-language behavior.

External Transmission

Medium
Category
Data Exfiltration
Content
data["seed"] = seed
    
    print(f"正在创建视频生成任务...")
    response = requests.post(API_URL, headers=headers, json=data)
    result = response.json()
    
    if "id" not in result:
Confidence
80% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Tainted flow: 'task_id' from requests.post (line 114, network input) → requests.get (network output)

Medium
Category
Data Flow
Content
start_time = time.time()
    while time.time() - start_time < timeout:
        query_response = requests.get(f"{QUERY_URL}/{task_id}", headers=headers)
        query_result = query_response.json()
        
        status = query_result.get("status", "unknown")
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.

Tainted flow: 'video_url' from requests.get (line 127, network input) → requests.get (network output)

Medium
Category
Data Flow
Content
filepath = os.path.join(output_dir, filename)
                
                print(f"正在下载视频...")
                video_response = requests.get(video_url)
                with open(filepath, "wb") as f:
                    f.write(video_response.content)
                local_paths.append(filepath)
Confidence
90% confidence
Finding
The code downloads a URL supplied by the remote API without validating its scheme, host, or size. If the upstream service is compromised, misconfigured, or attacker-influenced, this can enable server-side request forgery-style outbound requests or downloading unexpected content to disk.

Missing User Warnings

Low
Confidence
84% confidence
Finding
The skill requires a VOLCENGINE_API_KEY but provides no guidance on secure credential handling or notice that the skill depends on a third-party service. This increases the risk of poor secret management practices, accidental exposure of credentials, or user confusion about trust and availability boundaries.

Intent-Code Divergence

Low
Confidence
95% confidence
Finding
The `generate_video` docstring says it calls '火山引擎 Seedream 生成视频', but the module name, manifest, model identifiers, and API endpoint all indicate this code is for Seedance video generation. This is an active documentation contradiction rather than a mere omission, because it labels the operation as a different service/product.

Static analysis

No suspicious patterns detected.