Member Junction
    Preparing search index...
    • A drop-in replacement for fetch that 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:

      1. Every hop is validated. Automatic redirects are disabled (redirect: 'manual') and each 3xx Location is resolved against the current URL and re-run through AssertPublicUrl before being followed. A public URL that 302s to http://169.254.169.254/ is caught on the second hop, which is exactly what a naive check misses.
      2. Every address is checked, not just the first — see AssertPublicUrl.

      Intermediate response bodies are cancelled as it goes, so following a chain does not leak sockets.

      Parameters

      • rawUrl: string

        the caller-controlled URL to fetch.

      • Optionalinit: SafeFetchInit

        standard fetch init, plus an optional MaxRedirects (default 5). Any redirect value is ignored; redirects are always driven manually.

      Returns Promise<Response>

      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.

      when the redirect limit is exceeded.

      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();
      try {
      return await SafeFetch(url);
      } catch (error) {
      if (error instanceof SSRFError) {
      // A security decision — do not retry, do not treat as an outage.
      return { Success: false, ResultCode: 'SSRF_BLOCKED' };
      }
      throw error;
      }