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. MirrorsView on GitHub (pinned to 64b1b85502)
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
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
- 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
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
- Could not resolve host "{parsed.hostname}"
- Refusing to talk to "{hostname}": the connection was establi
- Refusing to fetch "${host}" — it resolves to a private, loop
- Unsafe path component: {e}
- Error downloading file: {_sanitize_url_for_logging(self.s3ur
AI-assisted analysis of ComposioHQ/composio@64b1b85502 (2026-08-28).
Data as JSON: /api/errors/f056ac725bbe6ab0.
Report an issue: GitHub.