hiyouga/LlamaFactory · error · HTTPException
Access to private or reserved IP addresses is not allowed.
Error message
Access to private or reserved IP addresses is not allowed.
What it means
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.
Source
Thrown at src/llamafactory/api/common.py:86
def check_ssrf_url(url: str) -> None:
"""Checks if a given URL is vulnerable to SSRF. Raises HTTPException if unsafe."""
try:
parsed_url = urlparse(url)
if parsed_url.scheme not in ["http", "https"]:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Only HTTP/HTTPS URLs are allowed.")
hostname = parsed_url.hostname
if not hostname:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid URL hostname.")
ip_info = socket.getaddrinfo(hostname, parsed_url.port)
ip_address_str = ip_info[0][4][0]
ip = ipaddress.ip_address(ip_address_str)
if not ip.is_global:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Access to private or reserved IP addresses is not allowed.",
)
except socket.gaierror:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST, detail=f"Could not resolve hostname: {parsed_url.hostname}"
)
except Exception as e:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=f"Invalid URL: {e}")
View on GitHub (pinned to f28afaf635)
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.
Example fix
// before
{type:'image_url', image_url:{url:'http://192.168.1.10:9000/bucket/img.png'}}
// after: base64 or public URL or local path
{type:'image_url', image_url:{url:'data:image/png;base64,iVBOR...'}} Defensive patterns
Strategy: validation
Validate before calling
import ipaddress, socket
from urllib.parse import urlparse
def url_is_public(u):
host = urlparse(u).hostname
try:
ip = ipaddress.ip_address(socket.getaddrinfo(host, None)[0][4][0])
return ip.is_global
except (socket.gaierror, ValueError):
return False Type guard
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'); }; Try / catch
catch (e) { if (e.status === 403 && e.detail.includes('private or reserved')) { return toDataUrl(media); } throw e; } Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Could not resolve hostname: {parsed_url.hostname}
- Local file access is disabled.
- File access is restricted to the safe media directory.
- Only HTTP/HTTPS URLs are allowed.
- Invalid URL hostname.
AI-assisted analysis of hiyouga/LlamaFactory@f28afaf635 (2026-08-14).
Data as JSON: /api/errors/8ec55c7b49c30771.
Report an issue: GitHub.