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 response

View on GitHub (pinned to 0b49e9b539)

Solutions

  1. Use a publicly reachable URL for the resource, or expose the internal service via a public/proxied endpoint.
  2. 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).
  3. Remove or narrow the offending entry in USER_ADDITIONAL_BANNED_SUBNETS if it wrongly covers your target IP.
  4. 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

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


AI-assisted analysis of HumanSignal/label-studio@0b49e9b539 (2026-08-29). Data as JSON: /api/errors/6c17dd750b60011e. Report an issue: GitHub.