- Location
- scripts/mcp_client.py:226
- Finding
- Untrusted Video URLs Permit Server-Side Request Forgery and Unbounded Downloads<![CDATA[
## Vulnerability Details
**File Location**: `scripts/mcp_client.py:226-249, 260-269`
**Vulnerability Type**: Client-side SSRF and unrestricted resource consumption
**Risk Level**: Medium
**Category**: T09: Insecure Skill Coding Practices
### Vulnerable Code
```python
def extract_video_url(payload: dict[str, Any]) -> str | None:
explicit = _find_string_by_keys(
payload,
(
"video_url",
"videoUrl",
"download_url",
"downloadUrl",
"result_url",
"resultUrl",
"output_url",
"outputUrl",
"file_url",
"fileUrl",
"signed_url",
"signedUrl",
),
)
if explicit and _is_http_url(explicit):
return explicit
generic = _find_string_by_keys(payload, ("url",))
if generic and _is_http_url(generic) and _looks_like_video_url(generic):
return generic
return None
```
```python
def download_binary(url: str) -> bytes:
req = urllib.request.Request(url, method="GET")
try:
with urllib.request.urlopen(req, timeout=240) as resp:
return resp.read()
except urllib.error.HTTPError as exc:
detail = exc.read().decode("utf-8", errors="replace")
raise RuntimeError(f"Signed URL HTTP {exc.code}: {detail}")
except urllib.error.URLError as exc:
raise RuntimeError(f"Signed URL network error: {exc.reason}")
```
The download sinks are reached from `scripts/generate.py:197-203` and `scripts/status.py:62-68`.
### Technical Analysis
The MCP response is treated as untrusted remote input, but any `http://` or `https://` value under an expected URL key is accepted as a video URL. The download function does not restrict the destination host or IP range, enforce HTTPS, validate the response content type, impose a maximum response size, or stream data with bounded storage.
Python's `urllib.request.urlopen` also follows ordinary HTTP redir
...[truncated 2348 chars]
- Remediation
- <![CDATA[
## Remediation Suggestions
1. Require HTTPS for all video download URLs.
2. Allowlist expected Filtrix storage or content-delivery domains where operationally possible.
3. Resolve the hostname before connecting and reject loopback, private, link-local, multicast, reserved, and unspecified IP ranges for both IPv4 and IPv6.
4. Repeat destination validation after every redirect and reject unapproved cross-origin redirects.
5. Defend against DNS rebinding by ensuring the validated address is the address used for the connection.
6. Enforce a strict maximum download size using both `Content-Length` checks and a streaming byte counter.
7. Stream downloads in bounded chunks directly to a temporary file instead of reading the entire response into memory.
8. Validate the response content type against an explicit set of supported video types.
9. Validate file signatures where practical and reject HTML, JSON, executable, or otherwise unexpected content.
10. Write to a temporary file first and atomically rename it only after all validation succeeds.
11. Apply separate connection, redirect, and read timeouts, and limit the number of redirects.
12. Add tests covering loopback URLs, RFC1918 addresses, IPv6 local addresses, cloud metadata addresses, redirects to private destinations, oversized payloads, and invalid content types.
]]>