Mitigating Server-Side Request Forgery (SSRF) in Modern Node.js & Next.js Applications
Deep dive into SSRF attack vectors against cloud metadata endpoints (169.254.169.254) and internal networks. How to build bulletproof URL validation proxies.

1. What is SSRF and Why is it Catastrophic in the Cloud?
Server-Side Request Forgery occurs when an attacker induces a server to send a crafted HTTP request to an unintended destination, such as internal microservices, private VPC endpoints, or cloud metadata instances.
In AWS, GCP, and Azure environments, internal metadata endpoints (like `http://169.254.169.254/latest/meta-data/`) allow unauthenticated requests from running instances to fetch temporary IAM credentials, leading to total cloud account takeovers.
2. The Flaw of Simple URL Parsing and Regex
Many developers attempt to prevent SSRF by checking whether the URL contains words like `localhost` or `127.0.0.1` using regex. This is completely ineffective against decimal representations (e.g. `2130706433`), hexadecimal IPs (`0x7f000001`), IPv6 loopbacks (`[::1]`), and DNS rebinding.
In a DNS rebinding attack, an attacker configures a domain name whose DNS record resolves to a public IP on the first check, but changes its TTL to 0 and resolves to `169.254.169.254` or `10.0.0.1` milliseconds later when the fetch occurs.
import dns from "node:dns/promises";
import net from "node:net";
const PRIVATE_SUBNETS = [
/^127\./, // Loopback
/^10\./, // Class A private
/^172\.(1[6-9]|2[0-9]|3[0-1])\./, // Class B private
/^192\.168\./, // Class C private
/^169\.254\./, // Cloud Link-Local Metadata
/^0\./, // Current network
];
export async function validateOutboundUrl(targetUrl: string): Promise<string> {
const parsed = new URL(targetUrl);
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
throw new Error("Protocol not permitted. Only HTTP and HTTPS allowed.");
}
// Resolve DNS to verify underlying IP addresses
const records = await dns.lookup(parsed.hostname, { all: true });
for (const { address } of records) {
if (net.isIPv4(address)) {
for (const subnet of PRIVATE_SUBNETS) {
if (subnet.test(address)) {
throw new Error(`Access to private IP space (${address}) is blocked.`);
}
}
} else if (net.isIPv6(address)) {
if (address === "::1" || address.startsWith("fc00:") || address.startsWith("fe80:")) {
throw new Error("Access to private IPv6 addresses is blocked.");
}
}
}
return targetUrl;
}3. Enforcing Strict Redirect Limits
Even if the initial URL is public, an attacker can configure their public server to issue an HTTP 302 redirect pointing directly to `http://169.254.169.254`. Standard HTTP libraries like `fetch` follow redirects automatically.
Always disable automatic redirects in your fetch client, validate the `Location` header manually against your SSRF filter, and limit total redirects to a maximum of 3 hops.