{"record":{"id":"6fcdbaf42b96002a","repo":"affaan-m/ECC","slug":"download-requires-https-on-an-approved-fal-media-host-6fcdba","errorCode":null,"errorMessage":"download requires HTTPS on an approved fal.media host","messagePattern":"download requires HTTPS on an approved fal\\.media host","errorType":"exception","errorClass":"FalError","httpStatus":null,"severity":"error","filePath":"skills/taste-distillation/scripts/taste/falapi.py","lineNumber":671,"sourceCode":"# ---------------------------------------------------------------------------\n# download\n# ---------------------------------------------------------------------------\n\n\nMAX_DOWNLOAD_BYTES = 2 * 1024 * 1024 * 1024  # bounded large video/GLB downloads\n\n\ndef _validate_download_url(url: str) -> None:\n    try:\n        parsed = urllib.parse.urlsplit(url)\n        host = parsed.hostname or \"\"\n        valid = (parsed.scheme == \"https\" and not parsed.username\n                 and not parsed.password and parsed.port in (None, 443)\n                 and (host == \"fal.media\" or host.endswith(\".fal.media\")))\n    except ValueError:\n        valid = False\n    if not valid:\n        raise FalError(\"download requires HTTPS on an approved fal.media host\")\n\n\nclass _SafeRedirect(urllib.request.HTTPRedirectHandler):\n    def redirect_request(self, req, fp, code, msg, headers, newurl):\n        _validate_download_url(newurl)\n        return super().redirect_request(req, fp, code, msg, headers, newurl)\n\n\ndef download(url: str, dest: str | Path) -> Path:\n    \"\"\"Bounded HTTPS download; failed transfers preserve existing destinations.\"\"\"\n    dest = Path(dest)\n    if is_dry_run():\n        dest.parent.mkdir(parents=True, exist_ok=True)\n        dest.write_bytes(b\"taste-forge dry-run placeholder\\n\")\n        log.info(\"[dry-run] would download from %s\", safe_url(url))\n        return dest\n\n    require_live()","sourceCodeStart":653,"sourceCodeEnd":689,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-distillation/scripts/taste/falapi.py#L653-L689","documentation":"falapi._validate_download_url enforces that any URL opened for downloading a generated asset uses HTTPS with no embedded credentials, port 443 or default, and a host of fal.media or a subdomain. urllib's opener and its redirect handler both call it, so even a 30x to a disallowed host is rejected. This prevents SSRF and mixed-content downloads from attacker-influenced redirect targets.","triggerScenarios":"Calling download() with an http:// URL, a URL whose host is not fal.media (e.g. a CDN host like fal-cdn.example.com), a URL containing user:pass@, an explicit non-443 port, or a malformed URL; also automatically when the server redirects to any such URL.","commonSituations":"Hard-coding an http:// link copied from an old integration; fal changing media hosts so stored URLs point elsewhere; constructing the URL by string concatenation that injects credentials; a redirect chain bouncing off fal.media to a generic CDN.","solutions":["Use the exact HTTPS URL returned by the fal API response, unmodified","Ensure the URL scheme is https and there is no userinfo or explicit port","If the host legitimately changed, whitelist-check the new host against fal.media before calling download","Catch FalError and log the offending URL (sanitized) to diagnose which constraint failed"],"exampleFix":"// before\ndownload(\"http://fal.media/files/abc.mp4\", dest)\n// after\ndownload(\"https://fal.media/files/abc.mp4\", dest)","handlingStrategy":"validation","validationCode":"from urllib.parse import urlparse\ndef is_downloadable(u):\n    try:\n        p = urlparse(u)\n        return p.scheme == \"https\" and not p.username and not p.password \\\n            and p.port in (None, 443) and (p.hostname == \"fal.media\" or p.hostname.endswith(\".fal.media\"))\n    except ValueError:\n        return False","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always use URLs exactly as returned by the fal API response object","Never string-build media URLs by hand","Keep a pre-flight urlparse check in your download helper"],"tags":["network","url-validation","security"],"backgroundTag":"invalid-url","analyzedSha":"8321021c54d670126ce3b2969d5deb880b4b0c2a","analyzedAt":"2026-09-16T10:08:13.343Z","contentChangedAt":"2026-09-16T10:08:13.343Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}