{"record":{"id":"f056ac725bbe6ab0","repo":"ComposioHQ/composio","slug":"refusing-to-fetch-parsed-hostname-because-it-r","errorCode":null,"errorMessage":"Refusing to fetch \"{parsed.hostname}\" because it resolves to a non-public address","messagePattern":"Refusing to fetch \"(.+?)\" because it resolves to a non-public address","errorType":"exception","errorClass":"BlockedInternalUrlError","httpStatus":null,"severity":"error","filePath":"python/composio/utils/url_safety.py","lineNumber":115,"sourceCode":"        # Resolver order is kept: it encodes the system's address preference\n        # (RFC 6724), and connecting walks it the way urllib3 would.\n        addresses: t.List[str] = []\n        for result in socket.getaddrinfo(parsed.hostname, None):\n            address = result[4][0]\n            if not isinstance(address, str):\n                raise BlockedInternalUrlError(\n                    f'Could not resolve host \"{parsed.hostname}\"'\n                )\n            if address not in addresses:\n                addresses.append(address)\n    except socket.gaierror as error:\n        raise BlockedInternalUrlError(\n            f'Could not resolve host \"{parsed.hostname}\"'\n        ) from error\n\n    for address in addresses:\n        if is_blocked_ip(address):\n            raise BlockedInternalUrlError(\n                f'Refusing to fetch \"{parsed.hostname}\" because it resolves to a non-public address'\n            )\n\n    if not addresses:\n        raise BlockedInternalUrlError(f'Could not resolve host \"{parsed.hostname}\"')\n\n    return addresses\n\n\ndef parse_content_length(value: t.Optional[str]) -> t.Optional[int]:\n    \"\"\"Parse a ``Content-Length`` header into a non-negative ``int``.\n\n    ``Content-Length`` is supplied by the remote server and is therefore\n    untrusted: it may be absent, non-numeric (``\"abc\"``), fractional\n    (``\"12.5\"``), thousands-separated (``\"1,024\"``) or negative. Anything\n    untrustworthy returns ``None`` so the caller treats the size as unknown\n    and falls through to a streamed byte count, which stays authoritative\n    because the header can also be absent or understated. Mirrors","sourceCodeStart":97,"sourceCodeEnd":133,"githubUrl":"https://github.com/ComposioHQ/composio/blob/64b1b85502b1beeb2379e6c9e8bf1104504fa637/python/composio/utils/url_safety.py#L97-L133","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Point the fetch at a genuinely public endpoint instead of localhost/private IPs","For local testing, run the mock on a public tunnel (ngrok etc.) or test with a stubbed transport instead of safe_request","If the target is legitimately internal, use requests directly rather than the safe-fetch wrapper","Check for DNS rebinding: confirm with dig/host that all A/AAAA records are public"],"exampleFix":"# before\nsafe_request('GET', 'http://localhost:9000/hook')\n# after\nsafe_request('GET', 'https://public-tunnel.example.dev/hook')","handlingStrategy":"validation","validationCode":"from composio.utils.url_safety import assert_safe_fetch_target\ntry:\n    assert_safe_fetch_target(url)\nexcept BlockedInternalUrlError:\n    raise ValueError('target is not a safe public URL')","typeGuard":"import ipaddress\n\ndef is_safe_public_host(hostname: str) -> bool:\n    try:\n        infos = socket.getaddrinfo(hostname, None)\n    except socket.gaierror:\n        return False\n    for info in infos:\n        ip = ipaddress.ip_address(info[4][0])\n        if not ip.is_global:\n            return False\n    return True","tryCatchPattern":"try:\n    safe_request('GET', url)\nexcept BlockedInternalUrlError as e:\n    if 'non-public address' in str(e):\n        skip_or_flag(url)  # SSRF guard; do not bypass","preventionTips":["Never feed user-supplied URLs with internal hosts to safe_request","Use public tunnel hosts for local test targets","Treat the block as a security feature, not a bug"],"tags":["security","ssrf","network","python"],"backgroundTag":"ssrf-blocked-private-address","analyzedSha":"64b1b85502b1beeb2379e6c9e8bf1104504fa637","analyzedAt":"2026-08-28T15:39:33.623Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}