T09 · Insecure Skill Coding Practices
- Location
scripts/video_remove.py:75- Finding
Unvalidated Result URL Enables Server-Side Request Forgery and Resource Exhaustion
- Content
View full analysis
Vulnerability Details
File Location:
scripts/video_remove.py:75-78; remotely supplied values reach this function at lines 145 and 313
Vulnerability Type: Unvalidated outbound request, SSRF, and unbounded response handling
Risk Level: MediumVulnerable Code
python def download_result(url, output_path): resp = requests.get(url) with open(output_path, "wb") as f: f.write(resp.content)The function is invoked using result URLs supplied by the remote API:
python download_result(seg_data["video_path"], mask_raw)python download_result(erase_data["video_path"], output)Technical Analysis
The
video_pathfield is controlled by the remote processing service and is passed directly torequests.get()without validating its scheme, hostname, resolved IP address, destination port, or redirect chain.Because
requests.get()follows redirects by default, validation would also need to cover every redirect target. A compromised, malicious, or incorrectly configured processing service could return a URL targeting loopback, link-local, private-network, or otherwise internal services reachable from the machine running the Skill.The download also lacks:
- Connection and read timeouts
- HTTP status validation through
raise_for_status() - Streaming response handling
- A maximum response-size limit
- Content-type or media-format validation
resp.contentbuffers the entire response in memory before writing it to disk. An excessively large or non-terminating response could therefore consume substantial memory, disk space, and execution time.Attack Path
- A user runs the Skill with a local video.
- The script uploads the video to the configured remote processing API.
- The processing API, or an attacker controlling its response path, returns an attacker-selected value in
video_path. - The script passes that value directly to `download ...[truncated 1265 chars]
- Remediation
View remediation
Remediation Suggestions
- Require result URLs to use HTTPS.
- Maintain an explicit allowlist of trusted object-storage or result-download hostnames.
- Resolve the hostname before connecting and reject loopback, private, link-local, multicast, reserved, and unspecified IP ranges for both IPv4 and IPv6.
- Disable redirects with
allow_redirects=False, or validate every redirect destination before following it. - Configure strict connection and read timeouts.
- Call
raise_for_status()before processing the response body. - Download with
stream=Trueand enforce a maximum byte count based on the expected video limits. - Validate the response content type and verify the downloaded file as an expected video or mask before further processing.
- Remove incomplete output files if validation or downloading fails.
- Prefer an API design that returns an opaque result identifier and downloads results only through a fixed, trusted endpoint.
Example hardened pattern:
python import ipaddress import socket from urllib.parse import urlparse ALLOWED_DOWNLOAD_HOSTS = {"trusted-result-host.example"} def validate_download_url(url): parsed = urlparse(url) if parsed.scheme != "https" or parsed.hostname not in ALLOWED_DOWNLOAD_HOSTS: raise ValueError("Untrusted result URL") for result in socket.getaddrinfo(parsed.hostname, parsed.port or 443): address = ipaddress.ip_address(result[4][0]) if not address.is_global: raise ValueError("Non-public result address") def download_result(url, output_path, max_bytes=50 * 1024 * 1024): validate_download_url(url) total = 0 try: with requests.get( url, stream=True, timeout=(5, 30), allow_redirects=False, ) as resp: resp.raise_for_status() with open(output_path, "wb") as output: for chunk in r ...[truncated 437 chars]
