{"record":{"id":"ef589230cc6bad46","repo":"ComposioHQ/composio","slug":"refusing-to-fetch-too-many-redirects-max-maxre","errorCode":null,"errorMessage":"Refusing to fetch: too many redirects (max ${maxRedirects})","messagePattern":"Refusing to fetch: too many redirects \\(max (.+?)\\)","errorType":"exception","errorClass":"ComposioBlockedInternalUrlError","httpStatus":null,"severity":"error","filePath":"ts/packages/core/src/utils/ssrfGuard.node.ts","lineNumber":280,"sourceCode":"    // body still being streamed to the caller — before releasing the socket.\n    if (dispatcher !== undefined) {\n      void dispatcher.close().catch(() => undefined);\n    }\n\n    const isRedirect =\n      response.status >= 300 && response.status < 400 && response.headers.has('location');\n    if (!isRedirect) {\n      return response;\n    }\n\n    // Only `location` is read from a redirect, so release its body explicitly rather\n    // than leaving it to the garbage collector (mirrors `readResponseBodyWithLimit`).\n    await response.body?.cancel().catch(() => undefined);\n\n    currentUrl = new URL(response.headers.get('location')!, currentUrl).toString();\n  }\n\n  throw new ComposioBlockedInternalUrlError(\n    `Refusing to fetch: too many redirects (max ${maxRedirects})`,\n    { url: rawUrl }\n  );\n};\n\n/**\n * {@link ssrfSafeFetch} for call sites that must keep working on every runtime.\n *\n * `ssrfSafeFetch` fails closed in edge runtimes, which is right for a URL the\n * caller chose to upload — the alternative is fetching it unvalidated — but not\n * for Tool Router session file transfers, where refusing would remove a working\n * feature from Workers rather than close a hole reachable there. On Node this\n * is the full guard; only the edge build differs.\n */\nexport const ssrfSafeFetchWhereSupported = ssrfSafeFetch;\n","sourceCodeStart":262,"sourceCodeEnd":296,"githubUrl":"https://github.com/ComposioHQ/composio/blob/64b1b85502b1beeb2379e6c9e8bf1104504fa637/ts/packages/core/src/utils/ssrfGuard.node.ts#L262-L296","documentation":"ssrfSafeFetch follows redirects manually, re-validating each hop through the SSRF checks, up to maxRedirects. When the chain exceeds the limit it stops and throws this error rather than following further — preventing redirect loops and bypass chains.","triggerScenarios":"A URL whose redirect chain is longer than maxRedirects — typically an HTTP->HTTPS + trailing-slash + auth cascade, a redirect loop (A->B->A), or intentionally long chains used to evade validation.","commonSituations":"URL shorteners stacked on shorteners; misconfigured servers bouncing between www/non-www and http/https repeatedly; sign-in-redirect pages that keep redirecting.","solutions":["Resolve the final URL yourself first (follow redirects with your own fetch) and pass the final direct URL","Fix the origin server's redirect loop if you control it","Avoid stacked URL shorteners in uploaded URLs"],"exampleFix":"// before\nawait upload.uploadFileAtUrl('https://bit.ly/a-b-c-d-e');\n\n// after\nconst finalUrl = new URL(await fetch(url, { redirect: 'follow' })).url;\n// ensure finalUrl is a direct, non-redirecting public URL\nawait upload.uploadFileAtUrl(finalUrl);","handlingStrategy":"fallback","validationCode":"// pre-resolve redirects yourself and pass the final URL\nconst probe = await fetch(url, { redirect: 'follow' });\nconst finalUrl = probe.url; // ensure it no longer redirects","typeGuard":null,"tryCatchPattern":"try {\n  await upload.uploadFileAtUrl(url);\n} catch (e) {\n  if (e instanceof ComposioBlockedInternalUrlError && /too many redirects/.test(e.message)) {\n    const finalUrl = (await fetch(url, { redirect: 'follow' })).url;\n    return upload.uploadFileAtUrl(finalUrl);\n  }\n}","preventionTips":["Dereference shorteners and redirect chains before passing URLs","Avoid stacked URL shorteners in uploaded links","Fix redirect loops (www/http alternation) on servers you control"],"tags":["ssrf","redirects","network","url-validation"],"backgroundTag":"too-many-redirects","analyzedSha":"64b1b85502b1beeb2379e6c9e8bf1104504fa637","analyzedAt":"2026-08-28T15:39:33.623Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}