the caller-controlled URL to fetch.
Optionalinit: SafeFetchInitstandard fetch init, plus an optional MaxRedirects (default 5). Any redirect
value is ignored; redirects are always driven manually.
the final Response, after following any redirects that passed validation. The
body is unread, so the caller chooses .json(), .text(), .arrayBuffer(), and so on.
when the initial URL — or any hop — is malformed, uses a non-http(s) scheme, fails to resolve, or resolves to a private or reserved address.
const response = await SafeFetch(userSuppliedUrl, {
headers: { 'User-Agent': 'MemberJunction/1.0' },
signal: AbortSignal.timeout(10000),
MaxRedirects: 5,
});
if (!response.ok) return { Success: false, ResultCode: `HTTP_${response.status}` };
const html = await response.text();
A drop-in replacement for
fetchthat is safe against SSRF. This is the function to reach for whenever the URL is not one you hard-coded.Two things make it safe where a one-time hostname check is not:
redirect: 'manual') and each 3xxLocationis resolved against the current URL and re-run through AssertPublicUrl before being followed. A public URL that 302s tohttp://169.254.169.254/is caught on the second hop, which is exactly what a naive check misses.Intermediate response bodies are cancelled as it goes, so following a chain does not leak sockets.