{"record":{"id":"afcfa867f51771f4","repo":"hiyouga/LlamaFactory","slug":"only-http-https-urls-are-allowed","errorCode":null,"errorMessage":"Only HTTP/HTTPS URLs are allowed.","messagePattern":"Only HTTP/HTTPS URLs are allowed\\.","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"src/llamafactory/api/common.py","lineNumber":75,"sourceCode":"    try:\n        os.makedirs(SAFE_MEDIA_PATH, exist_ok=True)\n        real_path = os.path.realpath(path)\n        safe_path = os.path.realpath(SAFE_MEDIA_PATH)\n\n        if not real_path.startswith(safe_path):\n            raise HTTPException(\n                status_code=status.HTTP_403_FORBIDDEN, detail=\"File access is restricted to the safe media directory.\"\n            )\n    except Exception:\n        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=\"Invalid or inaccessible file path.\")\n\n\ndef check_ssrf_url(url: str) -> None:\n    \"\"\"Checks if a given URL is vulnerable to SSRF. Raises HTTPException if unsafe.\"\"\"\n    try:\n        parsed_url = urlparse(url)\n        if parsed_url.scheme not in [\"http\", \"https\"]:\n            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=\"Only HTTP/HTTPS URLs are allowed.\")\n\n        hostname = parsed_url.hostname\n        if not hostname:\n            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=\"Invalid URL hostname.\")\n\n        ip_info = socket.getaddrinfo(hostname, parsed_url.port)\n        ip_address_str = ip_info[0][4][0]\n        ip = ipaddress.ip_address(ip_address_str)\n\n        if not ip.is_global:\n            raise HTTPException(\n                status_code=status.HTTP_403_FORBIDDEN,\n                detail=\"Access to private or reserved IP addresses is not allowed.\",\n            )\n\n    except socket.gaierror:\n        raise HTTPException(\n            status_code=status.HTTP_400_BAD_REQUEST, detail=f\"Could not resolve hostname: {parsed_url.hostname}\"","sourceCodeStart":57,"sourceCodeEnd":93,"githubUrl":"https://github.com/hiyouga/LlamaFactory/blob/f28afaf6355af515454dfb16c97d728307c93897/src/llamafactory/api/common.py#L57-L93","documentation":"Raised as HTTP 400 by check_ssrf_url when a media URL's scheme is not http or https. The SSRF guard first validates the scheme; data: URLs that reach this branch (they normally should not — base64 is matched earlier), ftp://, file://, gopher:// etc. are rejected before any DNS resolution.","triggerScenarios":"image_url/video_url/audio_url set to ftp://host/img.png, file:///etc/passwd, or a mistyped scheme like http//example.com/a.png that urlparse yields an empty/odd scheme for.","commonSituations":"Supplying file:// URLs expecting local-file behavior (should use local paths with ALLOW_LOCAL_FILES instead); copy-paste scheme typos; protocols the guard intentionally does not fetch.","solutions":["Use only http:// or https:// URLs for remote media.","For base64 payloads use the proper data:<mime>;base64,<data> form handled before this check.","For local files use a filesystem path with ALLOW_LOCAL_FILES=true, not file://.","Fix scheme typos (missing colon after http)."],"exampleFix":"// before\n{type:'image_url', image_url:{url:'file:///data/img.png'}}\n// after\nALLOW_LOCAL_FILES=true and url:'/safe-media/img.png'   // local path\n// or url:'https://cdn.example.com/img.png'","handlingStrategy":"validation","validationCode":"from urllib.parse import urlparse\ndef url_scheme_ok(u):\n    return urlparse(u).scheme in (\"http\", \"https\")\n\nassert all(url_scheme_ok(u) for u in media_urls)","typeGuard":"const schemeOk = (u) => ['http:', 'https:'].includes(new URL(u).protocol);","tryCatchPattern":"catch (e) { if (e.status === 400 && e.detail === 'Only HTTP/HTTPS URLs are allowed.') { u = toDataUrlOrHttps(u); retry; } throw e; }","preventionTips":["Reject file://, ftp://, and scheme-less URLs in the client before submission.","Validate with URL parsing, not string prefixes, to catch http// typos.","Keep base64 payloads in proper data:<mime>;base64, form."],"tags":["security","ssrf","url-validation","http-400"],"backgroundTag":null,"analyzedSha":"f28afaf6355af515454dfb16c97d728307c93897","analyzedAt":"2026-08-14T21:57:28.298Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}