← Back to API Security for AI-Assisted Development
API7:20234 min read read

Server Side Request Forgery (SSRF)

Block APIs from fetching attacker-controlled URLs and reaching internal infrastructure.

URL: trusteed.io/academy/api-security/server-side-request-forgery

Server Side Request Forgery (SSRF) occurs when an API fetches a remote resource based on a URL supplied by the client, without validating or restricting where that URL can actually point — allowing an attacker to make the server issue requests to internal infrastructure, cloud metadata endpoints, or otherwise unreachable systems, using the server's own network position and credentials.

What Is SSRF?

Any feature that accepts a URL and fetches it server-side — webhook configuration, image/file import from a link, URL preview generation, PDF-from-URL rendering, "connect your integration" callback validation — is a potential SSRF vector. Because the request originates from the server, it can reach destinations the attacker's own machine never could: internal admin panels not exposed to the internet, other services on the internal network, and — most critically in cloud environments — the instance metadata service (typically 169.254.169.254), which can return temporary cloud credentials with significant permissions if reachable.

Why AI Coding Assistants Get This Wrong

"Fetch this URL and process the response" is a common, simple-looking feature request, and the direct implementation — pass the client-supplied URL straight to an HTTP client — works correctly for every legitimate test case a developer tries. The SSRF risk only appears when someone supplies a URL pointing somewhere unexpected, which isn't something a functional test naturally exercises. Assistants also frequently don't account for redirect-following: even a URL that passes an initial validation check can redirect to an internal address, and default HTTP client behavior follows redirects transparently.

Vulnerable Pattern

// Direct pass-through — the server will fetch ANYTHING the client asks for
app.post('/api/import-image', authenticate, async (req, res) => {
  const response = await axios.get(req.body.imageUrl); // no validation at all
  res.json({ data: response.data });
});
POST /api/import-image
{ "imageUrl": "http://169.254.169.254/latest/meta-data/iam/security-credentials/" }

Secure Pattern

const axios = require('axios');
const dns = require('dns').promises;
const net = require('net');

const ALLOWED_HOSTS = ['images.trusted-partner.com', 'cdn.trusted-partner.com'];

function isPrivateIp(ip) {
  return (
    net.isIP(ip) && (
      /^127\./.test(ip) || /^10\./.test(ip) || /^192\.168\./.test(ip) ||
      /^172\.(1[6-9]|2\d|3[0-1])\./.test(ip) || ip === '169.254.169.254' ||
      ip === '::1' || ip.startsWith('fc') || ip.startsWith('fe80')
    )
  );
}

async function validateUrl(rawUrl) {
  const url = new URL(rawUrl); // throws on malformed input
  if (url.protocol !== 'https:') throw new Error('Only https allowed');
  if (!ALLOWED_HOSTS.includes(url.hostname)) throw new Error('Host not allowed');

  const addresses = await dns.resolve4(url.hostname); // resolve BEFORE fetching, check the real target
  if (addresses.some(isPrivateIp)) throw new Error('Resolves to a private address');
  return url;
}

app.post('/api/import-image', authenticate, async (req, res) => {
  try {
    const url = await validateUrl(req.body.imageUrl);
    const response = await axios.get(url.toString(), {
      maxRedirects: 0, // never follow redirects on server-side fetches
      timeout: 5000,
    });
    res.json({ data: response.data });
  } catch (err) {
    res.status(400).json({ error: 'Invalid or disallowed URL' });
  }
});

Validating the resolved IP, not just the hostname string, matters: DNS rebinding attacks change what a hostname resolves to between the validation check and the actual request unless you resolve once and use that address, or re-validate immediately before connecting.

Copy-Paste Rules for Cursor, Claude Code, and Codex

---
description: SSRF prevention — validating and restricting server-side outbound requests
alwaysApply: true
---

# SSRF Prevention Rules

- Never pass a client-supplied URL directly to an HTTP client (`axios.get`,
  `fetch`, `requests.get`, etc.) without validation.
- Maintain an explicit allow-list of permitted domains/hosts for any feature
  that fetches a URL server-side. Reject anything not on the allow-list —
  do not attempt to block a deny-list of "bad" hosts, which is incomplete
  by construction.
- Resolve the hostname to an IP address BEFORE issuing the request, and
  reject any resolution to a private/reserved IP range (RFC1918: 10.0.0.0/8,
  172.16.0.0/12, 192.168.0.0/16), loopback (127.0.0.0/8, ::1), or the cloud
  metadata address (169.254.169.254 and its IPv6 equivalent).
- Disable automatic redirect-following on server-side outbound requests
  (`maxRedirects: 0` or equivalent), or re-validate the redirect target with
  the same checks before following it manually.
- Restrict the allowed URL scheme explicitly (typically `https:` only) —
  never allow `file:`, `gopher:`, `ftp:`, or other non-HTTP schemes to reach
  a URL-fetching feature.
- Set an explicit timeout on every server-side outbound fetch.
- If the application runs in a cloud environment, ensure the service role
  used by URL-fetching code has no more permission than strictly necessary,
  as defense in depth against a validation bypass reaching the metadata service.

Verification Checklist

  • Every server-side URL-fetching feature validates against an explicit allow-list, not a deny-list
  • Hostname resolution is checked against private/reserved IP ranges before the request is made
  • Cloud metadata addresses (169.254.169.254 and IPv6 equivalent) are explicitly blocked
  • Redirects are disabled or re-validated on every server-side outbound fetch
  • Only expected URL schemes (typically https) are permitted
  • Timeouts are set on all outbound fetches
  • SSRF payloads are included in regular API security testing — see Trusteed's WAAP platform and vulnerability scanner for continuous testing that includes SSRF probing

Frequently asked questions

Is a deny-list of blocked hosts/IPs sufficient protection against SSRF?

No. Deny-lists are inherently incomplete — attackers can use alternate IP representations (decimal, octal, IPv6-mapped IPv4), DNS rebinding, or newly discovered internal hostnames not yet on the list. An allow-list of explicitly permitted destinations is the more robust approach.

Does validating the URL string alone (without resolving DNS) prevent SSRF?

No. A hostname can pass a string-based check (e.g., it "looks like" an external domain) while resolving to a private IP address, either immediately or after a delay (DNS rebinding). Validation needs to check the resolved IP, not just the hostname text.

Are internal microservice-to-microservice calls also at risk of SSRF?

Yes, if a microservice accepts a URL parameter from an upstream (potentially attacker-influenced) request and fetches it without validation, the same SSRF risk applies regardless of whether the ultimate caller is an external user or another internal service.