{"record":{"id":"8ec55c7b49c30771","repo":"hiyouga/LlamaFactory","slug":"access-to-private-or-reserved-ip-addresses-is-not","errorCode":null,"errorMessage":"Access to private or reserved IP addresses is not allowed.","messagePattern":"Access to private or reserved IP addresses is not allowed\\.","errorType":"http","errorClass":"HTTPException","httpStatus":403,"severity":"error","filePath":"src/llamafactory/api/common.py","lineNumber":86,"sourceCode":"\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}\"\n        )\n    except Exception as e:\n        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=f\"Invalid URL: {e}\")\n","sourceCodeStart":68,"sourceCodeEnd":97,"githubUrl":"https://github.com/hiyouga/LlamaFactory/blob/f28afaf6355af515454dfb16c97d728307c93897/src/llamafactory/api/common.py#L68-L97","documentation":"Raised as HTTP 403 by check_ssrf_url when the resolved IP address of the media URL is not is_global — i.e. it resolves to private (10/8, 192.168/16, 172.16/12), loopback, link-local, or otherwise non-global space. This is the core SSRF mitigation: the server will not fetch media from internal network addresses.","triggerScenarios":"image_url pointing at http://localhost:8000/img.png, http://192.168.1.10/cam.jpg, http://10.0.0.5/file, or an internal DNS name (e.g. http://minio.internal/img.png) that resolves to a private IP; also 169.254.169.254-style metadata endpoints.","commonSituations":"Running the API inside a cluster and referencing internal object storage/minio by internal name; development against localhost-hosted media; DNS rebinding-adjacent setups where a public name resolves privately.","solutions":["Serve the media from a publicly resolvable host, or expose the internal service through a public endpoint/proxy.","For local media, switch to a local file path with ALLOW_LOCAL_FILES=true (goes through check_lfi_path instead).","Use base64 data: URLs for small assets to avoid network fetching entirely.","Note only ip_info[0] is checked — but do not rely on multi-A-record tricks; fix the addressing instead."],"exampleFix":"// before\n{type:'image_url', image_url:{url:'http://192.168.1.10:9000/bucket/img.png'}}\n// after: base64 or public URL or local path\n{type:'image_url', image_url:{url:'data:image/png;base64,iVBOR...'}}","handlingStrategy":"validation","validationCode":"import ipaddress, socket\nfrom urllib.parse import urlparse\ndef url_is_public(u):\n    host = urlparse(u).hostname\n    try:\n        ip = ipaddress.ip_address(socket.getaddrinfo(host, None)[0][4][0])\n        return ip.is_global\n    except (socket.gaierror, ValueError):\n        return False","typeGuard":"const isPublicUrl = async (u) => { const h = new URL(u).hostname; const r = await dns.lookup(h); return !ipaddr.parse(r.address).rangematch('private','loopback','linkLocal'); };","tryCatchPattern":"catch (e) { if (e.status === 403 && e.detail.includes('private or reserved')) { return toDataUrl(media); } throw e; }","preventionTips":["Resolve and classify media hostnames client-side before sending.","Do not point the API at localhost/private service addresses; expose media publicly or embed it.","Remember the server checks only the first DNS record — avoid multi-A internal hostnames."],"tags":["security","ssrf","network","http-403"],"backgroundTag":null,"analyzedSha":"f28afaf6355af515454dfb16c97d728307c93897","analyzedAt":"2026-08-14T21:57:28.298Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}