skip to content
$sarthak.giri
// security8 min read

SSRF in modern serverless — the metadata path that won't die

Cloud SSRF mitigations keep getting reinvented. Here's a pattern for defending edge functions properly.

Sarthak Giripublished 2026-04-12 · 8 min read

Server-Side Request Forgery is one of the oldest tricks in the OWASP playbook, yet every new compute platform seems to rediscover it the hard way. The story is always the same — a service fetches a URL the user controls, the URL points at an internal metadata endpoint, and the response leaks credentials the function didn't even know it had.

Why "serverless" doesn't fix it

The instinct is to assume that ephemeral functions sidestep the problem. They don't. Even on Vercel, AWS Lambda, or Cloudflare Workers:

  • The function runs inside a real environment with networked siblings.
  • Outbound fetches from the function still leave a node that other services trust by IP.
  • The platform may expose a metadata service on a predictable address (169.254.169.254 on AWS, similar on others) that returns secrets.

A user input like https://169.254.169.254/latest/meta-data/iam/security-credentials/ is the classic vector. If your code happily issues that fetch, you've handed over the keys.

The defensive pattern

Don't rely on string filtering — that lost the war years ago. Instead:

  1. Resolve the host yourself with the DNS resolver, then check the resolved IP against an explicit allowlist (or against a deny-set of RFC1918 + the metadata range).
  2. Disable redirects on the fetch, or follow them manually and re-validate the new host every hop.
  3. Use a constrained HTTP client that refuses connections to private CIDR blocks by default.

In Node:

import { lookup } from 'node:dns/promises';

const FORBIDDEN_RANGES = [
  '127.', '10.', '169.254.', '192.168.',
  // ...full RFC1918 + metadata
];

async function safeFetch(url: string) {
  const { hostname } = new URL(url);
  const { address } = await lookup(hostname);
  if (FORBIDDEN_RANGES.some((p) => address.startsWith(p))) {
    throw new Error('blocked-host');
  }
  return fetch(url, { redirect: 'manual' });
}

The harder lesson

Most SSRF findings I've reported weren't in obvious image-proxy code — they were in webhooks, OAuth callbacks, and "verify this URL works" endpoints. Anywhere your server fetches a user-controlled URL is a chance to leak the runtime environment. Treat it as a trust boundary.


Notes from labs, not production guidance. Pair with your cloud's IMDSv2 docs.