HumanSignal/label-studio · warning · SsrfBlockedUrlError
URL resolves to a reserved network address (block: {subnet})
Error message
URL resolves to a reserved network address (block: {subnet}) What it means
As part of SSRF protection, validate_ip compares the resolved IP against a set of banned subnets (default reserved ranges plus optional USER_ADDITIONAL_BANNED_SUBNETS). If the IP falls in any banned subnet, SsrfBlockedUrlError is raised naming the offending subnet — the URL points at a private/reserved network address.
Source
Thrown at label_studio/core/utils/io.py:265
'64:ff9b:1::/48', # IPv4/IPv6 translation
'100::/64', # discard prefix
'2001:0000::/32', # Teredo tunneling
'2001:20::/28', # ORCHIDv2
'2001:db8::/32', # documentation
'2002::/16', # 6to4
'fc00::/7', # unique local
'fe80::/10', # link-local
'ff00::/8', # multicast
]
banned_subnets = [
*(default_banned_subnets if settings.USE_DEFAULT_BANNED_SUBNETS else []),
*(settings.USER_ADDITIONAL_BANNED_SUBNETS or []),
]
for subnet in banned_subnets:
if ipaddress.ip_address(ip) in ipaddress.ip_network(subnet):
raise SsrfBlockedUrlError(f'URL resolves to a reserved network address (block: {subnet})')
def ssrf_safe_request(method, url, *args, **kwargs):
block_local_urls = kwargs.pop('block_local_urls', settings.SSRF_PROTECTION_ENABLED)
validate_url_for_ssrf(url, block_local_urls=block_local_urls)
# Reason for #nosec: url has been validated as SSRF safe by the
# validation check above.
response = requests.request(method, url, *args, **kwargs) # nosec
# second check for SSRF for prevent redirect and dns rebinding attacks
if block_local_urls:
try:
response_ip = response.raw._connection.sock.getpeername()[0]
validate_ip(response_ip)
except (AttributeError, TypeError, ValueError):
# Some adapters/mocks don't expose socket details.
pass
return responseView on GitHub (pinned to 0b49e9b539)
Solutions
- Use a publicly reachable URL for the resource, or expose the internal service via a public/proxied endpoint.
- If the target is legitimately internal and you accept the risk, disable SSRF protection via SSRF_PROTECTION_ENABLED=false (or pass block_local_urls=False where supported).
- Remove or narrow the offending entry in USER_ADDITIONAL_BANNED_SUBNETS if it wrongly covers your target IP.
- Check what IP the hostname resolves to (dig/nslookup) — you may be hitting an internal DNS record unintentionally.
Example fix
// before
ssrf_safe_request('GET', 'http://127.0.0.1:9000/presign') # blocked: loopback
// after: use a hostname resolving to an allowed IP, or explicitly allow
ssrf_safe_request('GET', 'http://storage.internal.example.com:9000/presign') # or SSRF_PROTECTION_ENABLED=false Defensive patterns
Strategy: validation
Validate before calling
import ipaddress, socket
from urllib.parse import urlparse
def is_publicly_routable(url: str) -> bool:
ip = ipaddress.ip_address(socket.gethostbyname(urlparse(url).hostname))
return not (ip.is_private or ip.is_loopback or ip.is_link_local or ip.is_reserved) Type guard
def url_ip_not_banned(url: str, banned: list[str]) -> bool:
ip = ipaddress.ip_address(socket.gethostbyname(urlparse(url).hostname))
return not any(ip in ipaddress.ip_network(s) for s in banned) Try / catch
try:
ssrf_safe_request('GET', url)
except SsrfBlockedUrlError as e:
logger.warning('Blocked SSRF-target URL %s: %s', url, e)
return None Prevention
- Only pass externally reachable URLs to upload/validation APIs.
- Keep USER_ADDITIONAL_BANNED_SUBNETS minimal and reviewed.
- Understand your SSRF_PROTECTION_ENABLED default before pointing at internal services.
- Resolve hostnames ahead of time to know what IP will be checked.
When it happens
Trigger: Calling validate_ip via validate_url_for_ssrf or ssrf_safe_request with a URL that resolves to a reserved address (127.0.0.1, 10.x, 172.16-31.x, 192.168.x, 169.254.x, etc.), or to any subnet listed in USER_ADDITIONAL_BANNED_SUBNETS.
Common situations: Pointing Label Studio at internal storage/services (localhost MinIO, internal metadata endpoints) while SSRF protection is enabled; USER_ADDITIONAL_BANNED_SUBNETS overlapping a legit corporate range; hostname resolving to a private IP behind a proxy.
Related errors
- Can't resolve hostname {domain}
- Directory indexes are not allowed here.
- extract_message(e)
- Failed to list storage files
- Absolute local path "{self.path}" cannot be the same as LOCA
AI-assisted analysis of HumanSignal/label-studio@0b49e9b539 (2026-08-29).
Data as JSON: /api/errors/6c17dd750b60011e.
Report an issue: GitHub.