← All news
security6 min read

SSRF vulnerabilities in AI-generated code that fetches URLs

By Seaworthy · 25 June 2026

Dense rows of colorful network cables plugged into server rack patch panels in a data center

Photo by Scott Rodgerson on Unsplash

The problem nobody thinks about until it's too late

AI coding assistants are good at generating HTTP client code. You describe what you need, and within seconds you have a function that takes a URL, fetches it, and returns the response. The problem is that "a URL" in the real world means any URL, including http://169.254.169.254/latest/meta-data/ on AWS, or http://localhost:6379 pointing at your Redis instance, or http://10.0.0.1/admin on your internal network.

This is server-side request forgery. SSRF. And AI tools generate vulnerable code for it constantly.

What AI-generated fetching code looks like

Ask any popular coding assistant to "write a function that fetches the content of a user-supplied URL." You'll get something like this:

async function fetchUserUrl(url: string): Promise<string> {
  const response = await fetch(url);
  if (!response.ok) {
    throw new Error(`HTTP error: ${response.status}`);
  }
  return response.text();
}

Clean, readable, works perfectly in tests. But it will cheerfully fetch http://169.254.169.254/latest/meta-data/iam/security-credentials/ from inside an EC2 instance and hand the response back to whoever called it. That response contains your AWS access keys.

The AI isn't wrong to generate this code for many use cases. It's wrong to generate it without a comment, a warning, or any validation when the whole point is that users are supplying the URL.

How attackers exploit it

The attack flow is straightforward. An attacker spots a feature that fetches a URL you provide, maybe an import tool, a webhook tester, a screenshot service, a link preview generator. They start probing:

  • http://127.0.0.1/ to confirm the server will make internal requests
  • http://169.254.169.254/latest/meta-data/ on AWS to grab cloud credentials
  • http://192.168.1.1/ to map internal network topology
  • file:///etc/passwd if the fetch library supports non-HTTP schemes

Cloud metadata endpoints are the most damaging target. AWS, GCP, and Azure all expose instance metadata at well-known addresses that are only reachable from within the cloud environment. An attacker who can make your server fetch those URLs can get IAM credentials, service account tokens, and enough information to pivot through your entire infrastructure.

What proper validation looks like

You need to block private IP ranges, loopback addresses, link-local addresses, and non-HTTP schemes before you ever call fetch. You also need to resolve the hostname first, because DNS rebinding attacks can map a public hostname to a private IP after your hostname check passes.

import { Resolver } from 'dns/promises';

const BLOCKED_CIDRS = [
  /^127\./,
  /^10\./,
  /^172\.(1[6-9]|2\d|3[01])\./,
  /^192\.168\./,
  /^169\.254\./,   // link-local / cloud metadata
  /^::1$/,
  /^fc00:/,
  /^fe80:/,
];

function isPrivateIp(ip: string): boolean {
  return BLOCKED_CIDRS.some(pattern => pattern.test(ip));
}

async function safeFetch(rawUrl: string): Promise<string> {
  let parsed: URL;
  try {
    parsed = new URL(rawUrl);
  } catch {
    throw new Error('Invalid URL');
  }

  if (!['http:', 'https:'].includes(parsed.protocol)) {
    throw new Error('Only HTTP and HTTPS URLs are allowed');
  }

  // Resolve the hostname and check all returned IPs
  const resolver = new Resolver();
  const addresses = await resolver.resolve(parsed.hostname);
  for (const addr of addresses) {
    if (isPrivateIp(addr)) {
      throw new Error('URL resolves to a private IP address');
    }
  }

  // Disable redirects or re-validate after each one
  const response = await fetch(rawUrl, { redirect: 'error' });
  if (!response.ok) {
    throw new Error(`HTTP error: ${response.status}`);
  }
  return response.text();
}

A few things worth noting here. The redirect: 'error' setting matters because a redirect can take you from a public address to a private one. If you need to follow redirects, you have to resolve and validate the destination URL at each hop. Also, this code resolves DNS once before fetching, but if your DNS TTL is very short and an attacker controls the DNS record, they could return a public IP during resolution and a private one during the actual connection. The real fix for that is to make the TCP connection to the resolved IP directly, not re-resolve on connect.

Why AI tools keep getting this wrong

The training data is full of fetch functions that work in controlled environments where the caller is trusted. The pattern of "user supplies URL, server fetches it" without validation is extremely common in tutorials, open source projects, and Stack Overflow answers from before SSRF was widely understood. The model learns to reproduce the pattern.

It also doesn't know your deployment environment. It doesn't know you're running on AWS where the metadata endpoint is a real threat. It generates code that satisfies the stated requirement, which was "fetch a URL."

This is exactly the gap that static analysis tools exist to fill. A linter won't tell you about SSRF, but a tool that understands the security implications of calling fetch or axios.get with user-supplied input can flag the pattern and explain why it's dangerous.

The fix in practice

The validation above is a starting point. In production you probably want to centralize it into a single wrapper module that every HTTP call in your codebase routes through, rather than trusting individual developers to remember to validate before every fetch. Make the safe version the easy version.

If you're using a service like a headless browser or a PDF renderer that makes its own HTTP requests, those need network-level controls too, because you can't validate URLs inside a subprocess.

Seaworthy flags SSRF-vulnerable fetch patterns during static analysis, including cases where user input flows into HTTP client calls without intervening validation.

This article was generated by AI and summarises publicly available sources.

From the makers of Seaworthy

Seaworthy scans your repo for the issues covered in articles like this one.

Security gaps, exposed secrets, and misconfigurations — caught before you deploy. Free to run, no account needed.

$ npx seaworthycode