Back to skill

Security audit

VoooAI - AI Multimedia NL2Workflow Platform

Security checks for vulnerabilities and agentic risk

Overview

This skill is mostly a disclosed VoooAI media-generation relay, but it has unsafe network and file-handling paths that could expose the user's access key or local/internal data if misused or misconfigured.

Review before installing. Only use this skill with a VOOOAI_ACCESS_KEY you are willing to use for media generation, do not set VOOOAI_BASE_URL except to a trusted VoooAI HTTPS origin, upload only files you intentionally selected, and avoid using direct --urls downloads from untrusted sources. Generated workflows may consume credits, so confirm costs before execution.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/_common.py:10
Finding
Bearer Access Key Can Be Transmitted to an Attacker-Controlled Server<![CDATA[ ## Vulnerability Details **File Location**: `scripts/_common.py`, lines 10-11 and 53-84; the same URL construction is used at lines 107-117 **Vulnerability Type**: Unrestricted authenticated API destination **Risk Level**: High ### Vulnerable Code ```python BASE_URL = os.environ.get("VOOOAI_BASE_URL", "https://voooai.com") ACCESS_KEY = os.environ.get("VOOOAI_ACCESS_KEY", "") ``` ```python def _headers(content_type: str = "application/json") -> dict: access_key = _ensure_access_key() headers = { "Authorization": f"Bearer {access_key}", } if content_type: headers["Content-Type"] = content_type return headers def api_get(path: str, timeout: int = 30) -> dict: validate_api_path(path) url = f"{BASE_URL.rstrip('/')}{path}" req = urllib.request.Request(url, method="GET", headers=_headers()) try: with urllib.request.urlopen(req, timeout=timeout) as resp: return json.loads(resp.read().decode("utf-8")) ``` The corresponding POST implementation uses the same pattern: ```python validate_api_path(path) url = f"{BASE_URL.rstrip('/')}{path}" data = json.dumps(body).encode("utf-8") req = urllib.request.Request( url, data=data, method="POST", headers=_headers(), ) try: with urllib.request.urlopen(req, timeout=timeout) as resp: return json.loads(resp.read().decode("utf-8")) ``` ### Technical Analysis `VOOOAI_BASE_URL` is accepted without validating its scheme, hostname, port, credentials, or relationship to the expected VoooAI origin. The API path allowlist only validates the path supplied to `api_get()` or `api_post()`; it does not validate the destination represented by `BASE_URL`. Every request adds `VOOOAI_ACCESS_KEY` as a bearer token. Consequently, a modified environment can direct authenticated requests to an arbitrary HTTP or HTTPS server. A non-HTTPS URL also exposes the token to network interception. Redirect behavior is not explicitly constrained, so ...[truncated 1377 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove arbitrary production overrides or maintain an explicit allowlist of approved API origins. 2. Parse the configured URL using `urllib.parse.urlsplit()` and require: - The `https` scheme. - An exact approved hostname, such as `voooai.com`, or a narrowly defined subdomain allowlist. - No embedded username or password. - No fragment. - Only an explicitly approved port. 3. Construct URLs using safe URL-joining logic rather than string concatenation. 4. Install a redirect handler that rejects cross-origin redirects for authenticated requests. 5. Never forward the `Authorization` header after a scheme, host, or port change. 6. If development servers are required, use a separate development credential and explicit configuration flag rather than accepting an unrestricted environment value. 7. Add tests covering HTTP URLs, lookalike domains, embedded credentials, unexpected ports, and cross-origin redirects. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/upload_file.py:34
Finding
Media Upload Validation Can Be Bypassed to Upload Arbitrary Local Files<![CDATA[ ## Vulnerability Details **File Location**: `scripts/upload_file.py`, lines 34-86 **Vulnerability Type**: Insufficient local-file type validation **Risk Level**: Medium ### Vulnerable Code ```python # 检查文件大小 file_size = os.path.getsize(file_path) max_size_mb = 200 # 视频最大 200MB if file_size > max_size_mb * 1024 * 1024: error_exit(f"文件过大: {file_size / 1024 / 1024:.1f}MB(最大 {max_size_mb}MB)") # 检查 MIME 类型 mime_type, _ = mimetypes.guess_type(file_path) if mime_type and not any(mime_type.startswith(p) for p in ALLOWED_PREFIXES): error_exit(f"不支持的文件类型: {mime_type},仅支持图片、视频和音频") # 构建 multipart/form-data 请求体 boundary = f"----VoooAIUpload{uuid.uuid4().hex}" filename = os.path.basename(file_path) content_type = mime_type or "application/octet-stream" body_parts = [] # file 字段 body_parts.append(f"--{boundary}\r\n".encode()) body_parts.append( f'Content-Disposition: form-data; name="file"; filename="{filename}"\r\n'.encode() ) body_parts.append(f"Content-Type: {content_type}\r\n\r\n".encode()) with open(file_path, "rb") as f: body_parts.append(f.read()) body_parts.append(b"\r\n") # 结束边界 body_parts.append(f"--{boundary}--\r\n".encode()) data = b"".join(body_parts) # 校验路径(/api/upload) api_path = "/api/upload" validate_api_path(api_path) # 获取 AccessKey access_key = _ensure_access_key() url = f"{BASE_URL.rstrip('/')}{api_path}" req = urllib.request.Request( url, data=data, method="POST", headers={ "Authorization": f"Bearer {access_key}", "Content-Type": f"multipart/form-data; boundary={boundary}", }, ) ``` ### Technical Analysis The code relies on `mimetypes.guess_type()`, which infers a MIME type from the filename rather than examining the file content. Validation is performed only when `mime_type` is truthy: ```python if mime_type and not any(...): ``` If an extensionless file or an unrecognized extension causes `guess_type()` to return `None`, validation is skipped and the file is uploaded as `appl ...[truncated 1515 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Reject files for which MIME type detection is unavailable; do not treat unknown types as acceptable. 2. Apply an explicit, case-insensitive extension allowlist matching the documented formats. 3. Validate actual file signatures using a maintained media parser or magic-number library. 4. Require the detected content type, extension, and expected media category to agree. 5. Resolve the path with `os.path.realpath()` and present the exact resolved path for user confirmation before upload. 6. Consider restricting uploads to user-selected working directories rather than allowing arbitrary filesystem locations. 7. Implement the documented category-specific size limits instead of applying 200 MB to every file. 8. Stream multipart content from disk rather than loading the whole file and creating a second joined copy in memory. 9. Add tests for extensionless files, `.env` files, renamed text files, malformed media, symlinks, and oversized image/audio files. ]]>

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/download_results.py:73
Finding
Unrestricted Result URLs Enable Local File Retrieval and Internal Network Access<![CDATA[ ## Vulnerability Details **File Location**: `scripts/download_results.py`, lines 73-96 and 194-205 **Vulnerability Type**: Arbitrary URL retrieval, local file access, and server-side request forgery **Risk Level**: High ### Vulnerable Code ```python def download_file(url: str, filepath: str) -> tuple: """ 下载单个文件 Args: url: 文件 URL filepath: 保存路径 Returns: (filepath, error) - error 为 None 表示成功 """ req = urllib.request.Request(url, headers={"User-Agent": "VoooAI-Skill/1.0"}) try: with urllib.request.urlopen(req, timeout=120) as resp: with open(filepath, "wb") as f: while True: chunk = resp.read(8192) if not chunk: break f.write(chunk) return filepath, None except Exception as e: return filepath, str(e) ``` Direct URLs are accepted without validation: ```python download_items = [] if args.urls: # 直接指定的 URL for url in args.urls: download_items.append({ "url": url, "type": "unknown", "node_id": "", }) ``` URLs extracted from backend execution results also reach the same download function without scheme or destination validation. ### Technical Analysis `urllib.request.urlopen()` supports more than ordinary public HTTPS downloads. The input is not constrained to HTTPS, and there are no checks for: - `file://` URLs. - Loopback addresses. - Private network ranges. - Link-local addresses. - Cloud metadata endpoints. - DNS rebinding. - Cross-origin redirects. - Untrusted backend-provided output URLs. As a result, a direct `--urls` argument can copy readable local files into the output directory. HTTP URLs can cause requests to internal services that are reachable from the host but not from the attacker. Backend-provided result URLs create the same exposure if the service response is compromised or contains ...[truncated 1611 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Parse every URL before use and allow only the `https` scheme. 2. Reject URLs containing embedded credentials or unexpected ports. 3. Prefer an allowlist of trusted VoooAI result-storage hostnames. 4. Resolve hostnames and reject loopback, private, link-local, multicast, unspecified, and reserved IP ranges for both IPv4 and IPv6. 5. Re-resolve and revalidate the destination immediately before connecting to reduce DNS-rebinding exposure. 6. Disable redirects or manually process them, repeating the complete scheme, hostname, port, and resolved-address validation at every hop. 7. Explicitly reject `file:`, `ftp:`, `data:`, and all other non-HTTPS schemes. 8. Apply the same checks to direct command-line URLs and URLs obtained from backend execution results. 9. Consider removing `--urls` if arbitrary third-party downloads are not necessary for the declared workflow. 10. Add tests covering local files, loopback addresses, private IPv4 and IPv6 ranges, link-local metadata addresses, numeric host representations, DNS rebinding, and redirect chains. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/download_results.py:84
Finding
Unbounded Concurrent Downloads Can Exhaust Local Disk Space<![CDATA[ ## Vulnerability Details **File Location**: `scripts/download_results.py`, lines 84-93 and 267-291 **Vulnerability Type**: Unrestricted resource consumption **Risk Level**: Medium ### Vulnerable Code ```python req = urllib.request.Request(url, headers={"User-Agent": "VoooAI-Skill/1.0"}) try: with urllib.request.urlopen(req, timeout=120) as resp: with open(filepath, "wb") as f: while True: chunk = resp.read(8192) if not chunk: break f.write(chunk) return filepath, None except Exception as e: return filepath, str(e) ``` Downloads execute concurrently: ```python with ThreadPoolExecutor(max_workers=args.workers) as pool: futures = { pool.submit(download_file, url, fp): (url, fp, item) for url, fp, item in tasks } for future in as_completed(futures): url, fp, item = futures[future] filepath, err = future.result() if err: errors.append({ "file": filepath, "url": url, "error": err, }) print(f"✗ 下载失败: {os.path.basename(filepath)}", file=sys.stderr) else: results.append({ "file": filepath, "url": url, "type": item.get("type", "unknown"), "node_id": item.get("node_id", ""), }) ``` ### Technical Analysis The downloader streams each response until the remote endpoint closes the connection. It does not enforce: - A maximum size per file. - A maximum aggregate size. - A maximum number of result URLs. - A minimum available-disk threshold. - A trusted or bounded `Content-Length`. - Cleanup of partial files after failed or interrupted downloads. The `--workers` value is also accepted without an upper bound. Multiple large or endless responses can therefore consume disk space concurrently and increase file descriptor, memory, and netw ...[truncated 1305 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Enforce a conservative maximum size for each downloaded file. 2. Enforce a total byte budget across all downloads in one invocation. 3. Check `Content-Length` before downloading and reject responses exceeding the limit. 4. Maintain a byte counter while streaming because `Content-Length` may be absent or false. 5. Abort and delete the partial file immediately when a limit is exceeded. 6. Download to a safely created temporary file and atomically rename it only after successful validation. 7. Cap `--workers` to a small safe range and reject zero or negative values. 8. Limit the number of accepted URLs per invocation. 9. Check available filesystem capacity before and during large downloads. 10. Add connection, read, and overall transfer deadlines. 11. Clean up partial files following exceptions, cancellation, or process interruption. 12. Add tests using missing, incorrect, and oversized `Content-Length` values and endless streaming responses. ]]>
Vulnerability Patterns
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • 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
Findings (20)

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

Critical
Category
Data Flow
Content
url = f"{BASE_URL.rstrip('/')}{path}"
    req = urllib.request.Request(url, method="GET", headers=_headers())
    try:
        with urllib.request.urlopen(req, timeout=timeout) as resp:
            return json.loads(resp.read().decode("utf-8"))
    except urllib.error.HTTPError as e:
        err_body = e.read().decode("utf-8") if e.fp else ""
Confidence
97% confidence
Finding
The request destination is built from the environment-controlled BASE_URL and then used in urllib.request.urlopen while automatically attaching the Bearer access key in the Authorization header. If an attacker can influence VOOOAI_BASE_URL, they can redirect requests to an attacker-controlled host and capture the credential or proxy creative-operation requests, which is effectively SSRF plus secret exfiltration.

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

Critical
Category
Data Flow
Content
headers=_headers(),
    )
    try:
        with urllib.request.urlopen(req, timeout=timeout) as resp:
            return json.loads(resp.read().decode("utf-8"))
    except urllib.error.HTTPError as e:
        err_body = e.read().decode("utf-8") if e.fp else ""
Confidence
97% confidence
Finding
The POST path has the same issue: the code composes a URL from environment-controlled BASE_URL and sends authenticated requests with the Bearer token. A manipulated base URL can cause sensitive workflow inputs, uploaded-content metadata, and the access key itself to be sent to an unintended server.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The skill claims to be a passive relay, yet it supports direct execution of arbitrary workflow JSON/file/stdin and local parameter mutation via --set-param. That materially expands its authority from forwarding user prompts to executing attacker-controlled or unsafe workflow definitions, increasing the risk of misuse, unauthorized processing, or bypass of intended backend planning safeguards.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The skill claims to be a passive relay, yet it supports direct execution of arbitrary workflow JSON/file/stdin and local parameter mutation via --set-param. That materially expands its authority from forwarding user prompts to executing attacker-controlled or unsafe workflow definitions, increasing the risk of misuse, unauthorized processing, or bypass of intended backend planning safeguards.

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill declares access to environment variables, file handling, and network-backed operations but does not explicitly constrain its tool scope with a permissions or allowed-tools declaration. That creates unnecessary ambiguity about what the skill may access at runtime and weakens least-privilege enforcement, especially for a skill that handles uploads and uses an access key.

Vague Triggers

Medium
Confidence
95% confidence
Finding
The invocation guidance says to use the skill for any visual or audio content creation, generation, editing, or modification, which is broad enough to trigger the skill for many unrelated or higher-risk requests. Over-broad routing increases the chance that sensitive user content, copyrighted material, or unsafe media-manipulation requests are sent to the backend without sufficient narrowing or guardrails.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
| Direct API calls bypassing scripts | Security risk — use provided scripts only |
| Manual workflow JSON construction | Quality risk — use generate_workflow.py only |
| Deepfakes / non-consensual facial or voice mimicry | Content safety — violates ethical guidelines |
| Impersonating real individuals without consent | Content safety — privacy and legal concerns |
| Political disinformation / election interference | Content safety — harmful content prohibition |

## 5. Available Scripts
Confidence
75% confidence
Finding
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Vague Triggers

Medium
Confidence
88% confidence
Finding
The trigger pattern examples cover a very wide range of media-generation and editing requests without clearly defining hard boundaries for when human review, extra consent, or denial is required. In a media skill that supports uploads and transformations, such broad examples can cause over-invocation and accidental processing of sensitive or disallowed content.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
When `generate_workflow.py` returns `points_warning` or `estimated_points`:
1. **MUST show** estimated cost to user before execution
2. **MUST wait** for user confirmation
3. **NEVER auto-execute** when points_warning exists

**Example interaction:**
```
Confidence
85% confidence
Finding
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Description-Behavior Mismatch

Medium
Confidence
91% confidence
Finding
The script goes beyond the skill's stated role of relaying creative requests by querying and outputting account metadata such as points balance, membership level, trust level, rate limits, and possibly cost guidance. Even if obtained from a legitimate endpoint, exposing this information increases unnecessary data access and disclosure, which can leak operational/account details to users or downstream logs.

Natural-Language Policy Violations

Medium
Confidence
83% confidence
Finding
The file’s natural-language descriptions are entirely in Chinese, including the top-level docstring that defines the script’s purpose. Under the policy, forcing a specific language without user opt-in can be a locale/language policy violation, and this file does not mention any user choice or justified regional constraint.

Natural-Language Policy Violations

Medium
Confidence
97% confidence
Finding
The module docstring is written entirely in Chinese, and the command-line description/help strings throughout the script are also Chinese-only. This imposes a specific language on users without any opt-in or indication that the tool is intentionally locale-specific.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The script downloads arbitrary remote URLs returned by the backend or supplied via --urls and writes them directly to local disk without validating the source domain, content type, size, or warning the operator about trust boundaries. In this skill, the backend is explicitly trusted to generate and return media artifacts, so a compromised backend, malicious workflow output, or attacker-controlled URL could cause unsafe local file writes, storage exhaustion, or delivery of deceptive content under innocuous filenames.

Natural-Language Policy Violations

Medium
Confidence
94% confidence
Finding
The module docstring, CLI description, help text, examples, and runtime messages are written entirely in Chinese. This imposes a specific language on all users without any opt-in, alternate locale, or justification that the skill is region-specific, which matches the language/locale policy violation criteria.

Natural-Language Policy Violations

Medium
Confidence
96% confidence
Finding
The file's user-facing natural language is consistently Chinese, including the module docstring, CLI description, help text, error messages, and status output. This imposes a specific language on users without any opt-in or documented locale constraint, which matches the language/locale policy violation criteria.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The module docstring, CLI description, help text, examples, warnings, and status messages are all presented in Chinese. This effectively forces a specific language for users of the skill, and the file does not offer an opt-in choice for interface language or explain that the tool is intentionally region-specific.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
This file is a code file, so only SQP-2 and SQP-3 apply. Multiple user-facing strings beginning at the module docstring are fixed in Chinese, and the CLI help/output does not offer any language selection or explain that the tool is intended only for a Chinese-speaking or region-specific audience. That creates a natural-language locale policy concern under SQP-3.

Natural-Language Policy Violations

Low
Confidence
92% confidence
Finding
This code file contains natural-language content exclusively in Chinese in the module docstring and user-facing error output, with no indication that users can choose another language or that the skill is region-specific. That can violate a language/locale policy requiring user choice or explicit justification for a locale constraint.

Intent-Code Divergence

Low
Confidence
84% confidence
Finding
The documentation understates behavior by describing capability discovery while the code also retrieves and reports credit balance and account status details. This mismatch can cause operators, reviewers, or calling agents to authorize or invoke the script without realizing it accesses and exposes additional sensitive metadata.

Intent-Code Divergence

Low
Confidence
75% confidence
Finding
The documentation states that VOOOAI_ACCESS_KEY is only for creative operations, not billing/admin. However, the script contains logic for account points state and references a subscription/recharge URL when points are insufficient, which contradicts that narrow framing by exposing an account/subscription-related concern.

Static analysis

No suspicious patterns detected.