How to prevent SSRF when your server fetches a user-supplied URL
If your server fetches a URL that someone else supplied, check the resolved IP address, not just the hostname, before every request, including every redirect. Allow only http and https on ports 80 and 443, reject loopback, private, link-local (including the cloud metadata address 169.254.169.254), carrier-grade NAT, multicast and reserved ranges, follow redirects by hand, and cap bytes and time. npx secure-semgrep -L ssrf . finds fetches that skip the check.
What SSRF is
Server-side request forgery happens when your server fetches an address that someone outside your organization controls. They point it at a place only your server can reach: localhost, your internal network, or the cloud metadata address that hands out credentials. Link previews, webhooks, "import from URL" boxes and tools that look up a domain a visitor typed are the usual places it hides.
Checklist
- Scheme and port. Only
httpandhttps, only ports 80 and 443 unless you pin another. Rejectfile://,gopher://and every other scheme. - Resolve the host yourself and check every address it resolves to. Reject loopback (
127.0.0.0/8,::1), private (10/8,172.16/12,192.168/16,fc00::/7), link-local (169.254/16,fe80::/10), carrier-grade NAT (100.64/10), unspecified, broadcast, multicast and reserved ranges, and IPv4 addresses wrapped in IPv6 (mapped, NAT64, 6to4, Teredo). Never classify the raw host string:0x7f000001,2130706433and127.1all reach loopback. - Follow redirects by hand. Turn off automatic redirects, re-run the full check on each
Location, and cap hops at about 5. - Cap bytes and time. Connect timeout, total timeout and a maximum response size.
- Do not forward credentials or cookies across a redirect to another host.
- Parse once. Use the same parsed host for the check and the connection, and reject URLs with credentials before the at sign.
- Guard against DNS rebinding in high-risk code: resolve once, check that address, and connect to it directly while sending the original host name for TLS and virtual hosting.
- Validate stored URLs twice, on save and again right before each send. Prefer an allowlist where the product allows it.
Inputs your guard must reject
http://localhost/ http://127.0.0.1/ http://[::1]/
http://169.254.169.254/ http://0x7f000001/ http://2130706433/
http://0177.0.0.1/ http://127.1/ http://0.0.0.0/
http://[64:ff9b::a9fe:a9fe]/ http://[2002:7f00:1::]/ http://[::ffff:127.0.0.1]/
file:///etc/passwd gopher://127.0.0.1:6379/ http://127.0.0.1:22/
Also test a public-looking hostname that resolves to a private address, and a public URL that redirects to 169.254.169.254. Stub the resolver and the HTTP transport rather than relying on real DNS. It must still accept https://example.com/.
Find unchecked fetches
npx secure-semgrep -L ssrf .
The bundled SSRF rules cover JavaScript and TypeScript, Python and Rust (fetch, axios, got, requests, httpx, urllib, reqwest and similar). They flag a request whose URL is not a fixed string and does not pass through a guard function, a client that follows redirects automatically for such a URL, and TLS verification turned off (rejectUnauthorized: false, verify=False, danger_accept_invalid_certs(true)). Every rule is a warning: a non-literal URL from your own fixed config is fine.
Reference code
The ssrf-safe-fetch skill has guard implementations in Python (requests), TypeScript (Node fetch) and a Rust (reqwest) sketch, with the rebinding caveats for each. Install it for your coding agent:
npx skills add IsaacBell/secure-devtools --skill ssrf-safe-fetch