ComposioHQ/composio · error · BlockedInternalUrlError

Refusing to fetch "{parsed.hostname}" because it resolves to

Error message

Refusing to fetch "{parsed.hostname}" because it resolves to a non-public address

What it means

The URL's hostname resolves (at least in part) to a non-public address such as loopback, private RFC1918 space, link-local, or metadata IPs (169.254.169.254). This is the core SSRF guard: assert_safe_fetch_target checks every resolved address and blocks the fetch if any is not public.

Source

Thrown at python/composio/utils/url_safety.py:115

        # Resolver order is kept: it encodes the system's address preference
        # (RFC 6724), and connecting walks it the way urllib3 would.
        addresses: t.List[str] = []
        for result in socket.getaddrinfo(parsed.hostname, None):
            address = result[4][0]
            if not isinstance(address, str):
                raise BlockedInternalUrlError(
                    f'Could not resolve host "{parsed.hostname}"'
                )
            if address not in addresses:
                addresses.append(address)
    except socket.gaierror as error:
        raise BlockedInternalUrlError(
            f'Could not resolve host "{parsed.hostname}"'
        ) from error

    for address in addresses:
        if is_blocked_ip(address):
            raise BlockedInternalUrlError(
                f'Refusing to fetch "{parsed.hostname}" because it resolves to a non-public address'
            )

    if not addresses:
        raise BlockedInternalUrlError(f'Could not resolve host "{parsed.hostname}"')

    return addresses


def parse_content_length(value: t.Optional[str]) -> t.Optional[int]:
    """Parse a ``Content-Length`` header into a non-negative ``int``.

    ``Content-Length`` is supplied by the remote server and is therefore
    untrusted: it may be absent, non-numeric (``"abc"``), fractional
    (``"12.5"``), thousands-separated (``"1,024"``) or negative. Anything
    untrustworthy returns ``None`` so the caller treats the size as unknown
    and falls through to a streamed byte count, which stays authoritative
    because the header can also be absent or understated. Mirrors

View on GitHub (pinned to 64b1b85502)

Solutions

  1. Point the fetch at a genuinely public endpoint instead of localhost/private IPs
  2. For local testing, run the mock on a public tunnel (ngrok etc.) or test with a stubbed transport instead of safe_request
  3. If the target is legitimately internal, use requests directly rather than the safe-fetch wrapper
  4. Check for DNS rebinding: confirm with dig/host that all A/AAAA records are public

Example fix

# before
safe_request('GET', 'http://localhost:9000/hook')
# after
safe_request('GET', 'https://public-tunnel.example.dev/hook')
Defensive patterns

Strategy: validation

Validate before calling

from composio.utils.url_safety import assert_safe_fetch_target
try:
    assert_safe_fetch_target(url)
except BlockedInternalUrlError:
    raise ValueError('target is not a safe public URL')

Type guard

import ipaddress

def is_safe_public_host(hostname: str) -> bool:
    try:
        infos = socket.getaddrinfo(hostname, None)
    except socket.gaierror:
        return False
    for info in infos:
        ip = ipaddress.ip_address(info[4][0])
        if not ip.is_global:
            return False
    return True

Try / catch

try:
    safe_request('GET', url)
except BlockedInternalUrlError as e:
    if 'non-public address' in str(e):
        skip_or_flag(url)  # SSRF guard; do not bypass

Prevention

When it happens

Trigger: safe_request against http://localhost, 127.0.0.1, 10.x/192.168.x/172.16-31.x hosts, ::1, or a public DNS name whose DNS rebinding/extra A record points to an internal IP; also 'localtest.me' style names that resolve to 127.0.0.1.

Common situations: Local development pointing the SDK at a local mock server, cloud instance trying to reach its own metadata endpoint, internal service hostnames resolved via search domains, DNS rebinding attacks.

Related errors


AI-assisted analysis of ComposioHQ/composio@64b1b85502 (2026-08-28). Data as JSON: /api/errors/f056ac725bbe6ab0. Report an issue: GitHub.