{"record":{"id":"23e4c0af1641ce83","repo":"affaan-m/ECC","slug":"download-requires-https-on-an-approved-fal-media-host-falapi","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-application/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-application/scripts/taste/falapi.py#L653-L689","documentation":"download() only fetches assets over HTTPS on hosts under fal.media (or a *.fal.media subdomain), with no embedded credentials and port 443 or default. This SSRF/asset-safety guard runs both before the initial request (via download) and on every redirect target (via _SafeRedirect.redirect_request); a URL that fails this check raises FalError.","triggerScenarios":"download('http://fal.media/x.mp4', ...) (plain HTTP); download from a non-fal.media host (e.g. a CDN URL returned by another provider or a signed s3/sr.se URL); a URL with embedded userinfo (https://user:pass@fal.media/...); a redirect issued by fal.media that points off-host to a different domain.","commonSituations":"Passing a URL from a different provider's CDN (e.g. replicate.delivery) into falapi.download; fal.ai introduces a new asset domain not yet allowlisted; testing against a localhost mirror; a model response contains a redirect to a third-party storage host.","solutions":["Only pass fal.media HTTPS URLs to download; download third-party URLs with your own HTTP client instead","If fal.ai now serves assets on a new domain, update the host allowlist in _validate_download_url to include it","Strip any userinfo from the URL and use plain https on port 443","Inspect redirect chains (curl -sIL) to confirm every hop stays on fal.media"],"exampleFix":"# before\ndownload('https://cdn.other-provider.com/mesh.glb', dest)\n# after\nif 'fal.media' not in urlparse(url).hostname:\n    raise ValueError('use a plain HTTP client for non-fal.media URLs')\ndownload(url, dest)","handlingStrategy":"validation","validationCode":"from urllib.parse import urlparse\nhost = urlparse(url).hostname or ''\nassert urlparse(url).scheme == 'https' and (host == 'fal.media' or host.endswith('.fal.media'))","typeGuard":"def is_fal_media_url(url: str) -> bool:\n    p = urlparse(url)\n    h = p.hostname or ''\n    return p.scheme == 'https' and (h == 'fal.media' or h.endswith('.fal.media'))","tryCatchPattern":"try:\n    download(url, dest)\nexcept FalError:\n    # fall back to a generic client for non-fal.media assets\n    import urllib.request; urllib.request.urlretrieve(url, dest)","preventionTips":["Only feed falapi.download URLs that came from fal.ai responses","Route third-party CDN URLs through your own HTTP client","Strip credentials and force port 443 on asset URLs","Watch for fal.ai asset-domain changes and update the allowlist"],"tags":["security","validation","url","download"],"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"}