ComposioHQ/composio · error · BlockedInternalUrlError
Refusing to fetch: too many redirects (max {max_redirects})
Error message
Refusing to fetch: too many redirects (max {max_redirects}) What it means
safe_request follows HTTP redirects manually (re-validating each hop) and enforces a hard cap. When the number of redirect hops exceeds max_redirects, it raises BlockedInternalUrlError instead of continuing, since each hop must be re-validated for SSRF safety.
Source
Thrown at python/composio/utils/url_safety.py:188
current_url = url
for _ in range(max_redirects + 1):
response = _pinned_request(method, current_url, **kwargs)
location = response.headers.get("Location")
if response.status_code not in _REDIRECT_STATUS_CODES or location is None:
return response
response.close()
current_url = urljoin(current_url, location)
# `requests` rewinds the body itself when it follows a redirect; doing
# it manually means doing that too, or a retried upload sends nothing.
seek = getattr(body, "seek", None)
if callable(seek):
seek(0)
raise BlockedInternalUrlError(
f"Refusing to fetch: too many redirects (max {max_redirects})"
)
class _PinnedAddressAdapter(requests.adapters.HTTPAdapter):
"""Transport adapter that connects to a pre-validated address.
The hostname is left untouched on the connection, so the ``Host`` header
and the TLS SNI/certificate check still use it; only the address the
socket dials is replaced. Doing it the other way round — rewriting
``conn._dns_host`` for the whole connection — would also rewrite
``conn.host``, which urllib3 derives from it, and the request would go out
with an IP in ``Host`` and an IP in SNI, failing certificate verification
against every real origin.
This reaches into two urllib3 internals, ``HTTPConnection._new_conn`` and
``HTTPConnection._dns_host``. ``test_url_safety_pinning.py`` asserts both
exist so a urllib3 upgrade that removes them fails loudly rather thanView on GitHub (pinned to 64b1b85502)
Solutions
- curl -IL <url> to inspect the redirect chain and find the loop
- Fix the server-side redirect loop (trailing slashes, http/https canonicalization)
- Request the final URL directly, bypassing the chain
- Pass a higher max_redirects to safe_request if the chain is legitimately long
Example fix
# before
safe_request('GET', url) # long auth redirect chain
# after
safe_request('GET', final_url_after_auth) # or safe_request('GET', url, max_redirects=20) Defensive patterns
Strategy: retry
Validate before calling
import requests r = requests.head(url, allow_redirects=False) # follow manually up to N hops to measure chain length before calling safe_request
Try / catch
try:
safe_request('GET', url)
except BlockedInternalUrlError as e:
if 'too many redirects' in str(e):
safe_request('GET', final_url) # resolve chain, or raise max_redirects Prevention
- Pre-resolve long auth chains to a final URL
- Pass an explicit max_redirects sized for your flow
- Watch for redirect loops in staging before production
When it happens
Trigger: Fetching a URL whose redirect chain is longer than max_redirects (default typically 10) — e.g. long SSO/auth chains, redirect loops (A→B→A), or misconfigured servers bouncing between trailing-slash and non-slash variants.
Common situations: Auth redirect loops after session expiry, CDN misconfiguration, presigned upload URLs that bounce through multiple regions, redirect loops between http and https.
Related errors
- Error downloading file: {_sanitize_url_for_logging(self.s3ur
- Failed to fetch file from URL: {e.cause}
- URL returned redirect. Please provide a direct URL to the fi
- Failed to download file: {message} (status_code, status_text
- Could not resolve host "{parsed.hostname}"
AI-assisted analysis of ComposioHQ/composio@64b1b85502 (2026-08-28).
Data as JSON: /api/errors/80944186f02d5b17.
Report an issue: GitHub.