ComposioHQ/composio · error · ComposioBlockedInternalUrlError
Refusing to fetch: too many redirects (max ${maxRedirects})
Error message
Refusing to fetch: too many redirects (max ${maxRedirects}) What it means
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.
Source
Thrown at ts/packages/core/src/utils/ssrfGuard.node.ts:280
// body still being streamed to the caller — before releasing the socket.
if (dispatcher !== undefined) {
void dispatcher.close().catch(() => undefined);
}
const isRedirect =
response.status >= 300 && response.status < 400 && response.headers.has('location');
if (!isRedirect) {
return response;
}
// Only `location` is read from a redirect, so release its body explicitly rather
// than leaving it to the garbage collector (mirrors `readResponseBodyWithLimit`).
await response.body?.cancel().catch(() => undefined);
currentUrl = new URL(response.headers.get('location')!, currentUrl).toString();
}
throw new ComposioBlockedInternalUrlError(
`Refusing to fetch: too many redirects (max ${maxRedirects})`,
{ url: rawUrl }
);
};
/**
* {@link ssrfSafeFetch} for call sites that must keep working on every runtime.
*
* `ssrfSafeFetch` fails closed in edge runtimes, which is right for a URL the
* caller chose to upload — the alternative is fetching it unvalidated — but not
* for Tool Router session file transfers, where refusing would remove a working
* feature from Workers rather than close a hole reachable there. On Node this
* is the full guard; only the edge build differs.
*/
export const ssrfSafeFetchWhereSupported = ssrfSafeFetch;
View on GitHub (pinned to 64b1b85502)
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
Example fix
// before
await upload.uploadFileAtUrl('https://bit.ly/a-b-c-d-e');
// after
const finalUrl = new URL(await fetch(url, { redirect: 'follow' })).url;
// ensure finalUrl is a direct, non-redirecting public URL
await upload.uploadFileAtUrl(finalUrl); Defensive patterns
Strategy: fallback
Validate before calling
// pre-resolve redirects yourself and pass the final URL
const probe = await fetch(url, { redirect: 'follow' });
const finalUrl = probe.url; // ensure it no longer redirects Try / catch
try {
await upload.uploadFileAtUrl(url);
} catch (e) {
if (e instanceof ComposioBlockedInternalUrlError && /too many redirects/.test(e.message)) {
const finalUrl = (await fetch(url, { redirect: 'follow' })).url;
return upload.uploadFileAtUrl(finalUrl);
}
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: URL shorteners stacked on shorteners; misconfigured servers bouncing between www/non-www and http/https repeatedly; sign-in-redirect pages that keep redirecting.
Related errors
- Could not resolve host "${host}"
- Refusing to fetch "${host}" — it resolves to a private, loop
- Refusing to fetch a malformed or non-http(s) URL
- Could not resolve host "{parsed.hostname}"
- Refusing to fetch "{parsed.hostname}" because it resolves to
AI-assisted analysis of ComposioHQ/composio@64b1b85502 (2026-08-28).
Data as JSON: /api/errors/ef589230cc6bad46.
Report an issue: GitHub.