T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/generate.py:24
- Finding
- Arbitrary Local File Disclosure Through Unvalidated Reference Image Path<![CDATA[ ## Vulnerability Details **File Location**: `scripts/generate.py`, lines 24-34 and 51-69 **Vulnerability Type**: Unrestricted local file read followed by network transmission **Risk Level**: Medium ### Vulnerable Code ```python def encode_image(path: str) -> dict: """Read an image file and return an OpenAI-style image_url content part.""" mime, _ = mimetypes.guess_type(path) if mime is None: mime = "image/png" with open(path, "rb") as f: b64 = base64.b64encode(f.read()).decode() return { "type": "image_url", "image_url": {"url": f"data:{mime};base64,{b64}"}, } def generate( prompt: str, output: str, ref_image: str | None = None, model: str = "google/gemini-3.1-flash-image-preview", api_key: str | None = None, base_url: str = "https://openrouter.ai/api/v1", ) -> str: """Generate an image and save to *output*. Returns the output path.""" api_key = api_key or os.environ.get("OPENROUTER_API_KEY") if not api_key: sys.exit("Error: OPENROUTER_API_KEY not set and --api-key not provided.") # Build message content content: list[dict] = [] if ref_image: content.append(encode_image(ref_image)) content.append({"type": "text", "text": prompt}) payload = { "model": model, "modalities": ["text", "image"], "messages": [{"role": "user", "content": content}], "max_tokens": 4096, } data = json.dumps(payload).encode() req = urllib.request.Request( f"{base_url}/chat/completions", data=data, headers={ "Authorization": f"Bearer {api_key}", "Content-Type": "application/json", }, method="POST", ) ``` ### Technical Analysis The `--ref` argument is documented as a reference image, but `encode_image()` accepts any readable path. The implementation relies on the filename extension to infer a MIME type and defaults unknown files to `im ...[truncated 2203 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. **Validate actual image content**: Parse the file with a trusted image decoder and reject data that cannot be decoded as a supported image format. Do not rely solely on the filename extension or `mimetypes.guess_type()`. 2. **Restrict permitted formats**: Allow only explicitly supported formats such as PNG, JPEG, and WebP, and derive the MIME type from validated content. 3. **Enforce path boundaries**: Resolve the path with `Path.resolve()` and require it to be under an approved workspace or media directory. Reject traversal, symlink escapes, and paths outside authorized roots. 4. **Set a file-size limit**: Check the file size before reading it and reject oversized references. This also limits memory exhaustion and excessive request sizes. 5. **Require informed confirmation**: If references outside an approved workspace must be supported, display the resolved path and destination service and require explicit confirmation before upload. 6. **Apply least privilege**: Run the Skill under an account or sandbox that cannot read unrelated credential stores, private keys, or system files. 7. **Document external disclosure clearly**: State that the selected reference image is transmitted to OpenRouter and may be processed under the provider's retention and privacy policies. 8. **Add security tests**: Verify that non-image files, oversized files, symlink escapes, traversal attempts, and paths outside approved roots are rejected before any network request occurs. ]]>
