We're sorry but this app doesn't work properly without JavaScript enabled. Please enable it to continue.

This lesson's interactive features are locked, please to keep using them

Defending Against SSRF

The safest SSRF defense is to avoid arbitrary destinations. If a travel app needs to fetch images from two providers, accept HTTPS URLs for those exact origins and reject everything else.

Or even better, accept image IDs and build the URLs within the server code.

For example, you can explicitly parse and validate the URL before any request is sent:

const ALLOWED_IMAGE_ORIGINS = new Set([
  "https://images.example.com",
  "https://cdn.example.com",
]);

function parseAllowedImageUrl(rawUrl: string): URL | undefined {
  let url: URL;
  try {
    url = new URL(rawUrl);
  } catch {
    return undefined;
  }

  if (url.protocol !== "https:") return undefined;
  if (url.username || url.password) return undefined;
  if (!ALLOWED_IMAGE_ORIGINS.has(url.origin)) return undefined;
  return url;
}

Compare parsed origins exactly. That will make sure you reject unexpected ports and lookalike hosts like images.example.com.attacker.test.

Also give outbound requests a deadline, so a slow approved host can't hold server resources indefinitely. You can pass AbortSignal.timeout() with a value in milliseconds as the fetch signal – e.g., AbortSignal.timeout(10_000) for a 10-second deadline.

Redirects need their own policy. fetch() follows redirects by default, which means an approved URL might redirect to an internal destination like 127.0.0.1. Disable automatic redirects with redirect: "manual" and reject redirect responses before reading or returning their bodies!

An exact-origin allowlist avoids most DNS ambiguity, but if you truly need arbitrary public hosts, blocking private addresses gets much harder: you have to validate every resolved IPv4 and IPv6 address and ensure the connection uses an address you checked. A separate DNS lookup followed by a normal fetch doesn't guarantee both operations use the same address, so it doesn't stop DNS rebinding by itself.

Assignment

Bearly Secure's image-preview helper can fetch any destination the server can reach. Restrict remote image previews to exact, approved HTTPS origins.

  1. http://127.0.0.1:3000/health
    
  2. https://storage.googleapis.com
    
  3. Use an HTTPS URL from an allowed image host.
    
  4. Image URL redirects are not allowed.
    

Run and submit the CLI tests from the project root.