{"record":{"id":"80944186f02d5b17","repo":"ComposioHQ/composio","slug":"refusing-to-fetch-too-many-redirects-max-max-re","errorCode":null,"errorMessage":"Refusing to fetch: too many redirects (max {max_redirects})","messagePattern":"Refusing to fetch: too many redirects \\(max (.+?)\\)","errorType":"exception","errorClass":"BlockedInternalUrlError","httpStatus":null,"severity":"error","filePath":"python/composio/utils/url_safety.py","lineNumber":188,"sourceCode":"    current_url = url\n\n    for _ in range(max_redirects + 1):\n        response = _pinned_request(method, current_url, **kwargs)\n\n        location = response.headers.get(\"Location\")\n        if response.status_code not in _REDIRECT_STATUS_CODES or location is None:\n            return response\n\n        response.close()\n        current_url = urljoin(current_url, location)\n\n        # `requests` rewinds the body itself when it follows a redirect; doing\n        # it manually means doing that too, or a retried upload sends nothing.\n        seek = getattr(body, \"seek\", None)\n        if callable(seek):\n            seek(0)\n\n    raise BlockedInternalUrlError(\n        f\"Refusing to fetch: too many redirects (max {max_redirects})\"\n    )\n\n\nclass _PinnedAddressAdapter(requests.adapters.HTTPAdapter):\n    \"\"\"Transport adapter that connects to a pre-validated address.\n\n    The hostname is left untouched on the connection, so the ``Host`` header\n    and the TLS SNI/certificate check still use it; only the address the\n    socket dials is replaced. Doing it the other way round — rewriting\n    ``conn._dns_host`` for the whole connection — would also rewrite\n    ``conn.host``, which urllib3 derives from it, and the request would go out\n    with an IP in ``Host`` and an IP in SNI, failing certificate verification\n    against every real origin.\n\n    This reaches into two urllib3 internals, ``HTTPConnection._new_conn`` and\n    ``HTTPConnection._dns_host``. ``test_url_safety_pinning.py`` asserts both\n    exist so a urllib3 upgrade that removes them fails loudly rather than","sourceCodeStart":170,"sourceCodeEnd":206,"githubUrl":"https://github.com/ComposioHQ/composio/blob/64b1b85502b1beeb2379e6c9e8bf1104504fa637/python/composio/utils/url_safety.py#L170-L206","documentation":"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.","triggerScenarios":"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.","commonSituations":"Auth redirect loops after session expiry, CDN misconfiguration, presigned upload URLs that bounce through multiple regions, redirect loops between http and https.","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"],"exampleFix":"# before\nsafe_request('GET', url)  # long auth redirect chain\n# after\nsafe_request('GET', final_url_after_auth)  # or safe_request('GET', url, max_redirects=20)","handlingStrategy":"retry","validationCode":"import requests\nr = requests.head(url, allow_redirects=False)\n# follow manually up to N hops to measure chain length before calling safe_request","typeGuard":null,"tryCatchPattern":"try:\n    safe_request('GET', url)\nexcept BlockedInternalUrlError as e:\n    if 'too many redirects' in str(e):\n        safe_request('GET', final_url)  # resolve chain, or raise max_redirects","preventionTips":["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"],"tags":["network","http","redirect","python"],"backgroundTag":"too-many-redirects","analyzedSha":"64b1b85502b1beeb2379e6c9e8bf1104504fa637","analyzedAt":"2026-08-28T15:39:33.623Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}