<< All versions

Skill v1.1.0

currentLLM-judged scan95/100
nguyencongnamit/skills-library/ssrf-prevention
──Details
PublishedOctober 4, 2026 at 03:21 AM
Content Hashsha256:d4e6365aa43fa393...
Git SHA
──Files
Files (1 file, 7.7 KB)
SKILL.md7.7 KBactive
SKILL.md · 149 lines · 7.7 KB

id: ssrf-prevention version: "1.1.0" title: "SSRF Prevention" description: "Defend against Server-Side Request Forgery: cloud metadata blocking, internal IP filtering, DNS rebinding defense, allowlist-based URL fetching" category: prevention severity: critical applies_to:

  • "when generating code that fetches a URL supplied by the client"
  • "when wiring webhooks, image proxies, PDF renderers, oEmbed fetchers"
  • "when running in any cloud environment with an instance metadata service"
  • "when reviewing a URL-parsing or HTTP-client wrapper"

languages: ["*"] token_budget: minimal: 1200 compact: 1600 full: 2600 rules_path: "rules/" related_skills: ["api-security", "cors-security", "iac-security"] last_updated: "2026-06-20" sources:

  • "OWASP SSRF Prevention Cheat Sheet"
  • "CWE-918: Server-Side Request Forgery"
  • "Capital One 2019 breach post-mortem (IMDSv1 SSRF)"
  • "AWS IMDSv2 documentation"
  • "PortSwigger Web Security Academy — SSRF labs"

SSRF Prevention

Rules (for AI agents)

ALWAYS

  • Validate every URL fetched on behalf of a client through an allowlist

of expected hosts. The allowlist is the only durable defense — block-lists are bypassable through encoding tricks, IPv6 dual-stack, and DNS rebinding.

  • Resolve the hostname once, validate the resolved IP against your

block-list of private / reserved / link-local ranges, then connect to that pinned IP using SNI. Otherwise an attacker can race a DNS rebind between validation and connect (time-of-check / time-of-use).

  • Block at the network layer and at the application layer. Drop egress to

169.254.169.254, [fd00:ec2::254], metadata.google.internal, and 100.100.100.200 from any service that doesn't legitimately need the metadata service.

  • Enforce IMDSv2 on AWS EC2 (session-token, hop-limit=1). IMDSv1 — the

pattern Capital One's 2019 breach exploited — must be disabled at the instance level.

  • Disable HTTP redirects by default on server-side fetchers (or follow only

a small bounded number, re-validating the new URL against the allowlist at each hop). The most common SSRF bypass is https://allowed.example.com returning a 302 to http://169.254.169.254/....

  • Use a separate, restricted HTTP client for user-controlled URLs vs

internal URLs. Misusing the wrong client must fail closed (e.g. via type-system distinction in Go / Rust / TypeScript).

  • Parse URLs with a single, well-known parser (Go net/url.Parse,

Python urllib.parse, JavaScript new URL()). Differential parsers between e.g. WHATWG and RFC-3986 are a documented SSRF bypass class.

NEVER

  • Trust a hostname / IP that was supplied by the user. Always re-resolve

in your trusted resolver and re-check the resolved address.

  • Connect to a URL based on its hostname when the protocol allows

redirects — gopher://, dict://, file://, jar://, netdoc://, ldap:// are all common SSRF amplifiers. Restrict to http:// and https:// (and ftp:// only if you actually need it).

  • Trust 0.0.0.0, 127.0.0.1, [::], [::1], localhost, or

*.localhost.test — all of them reach the local instance. The list also must include link-local 169.254.0.0/16, IPv4-mapped IPv6 ::ffff:127.0.0.1, and IPv6 ULA fc00::/7.

  • Use the user's URL string in a logging line or an error response —

it can be the SSRF reflection oracle that turns blind SSRF into data-exfiltration SSRF.

  • Run a metadata-blocking sidecar / proxy as the only defense — an

attacker who finds a Unix-domain-socket pseudo-URL or a misconfigured hostname can route around the proxy. Application-level allowlist remains required.

  • Ship a generic request forwarder that switches to fetching a **full,

caller-supplied URL** when the input "looks absolute" (path.startsWith('http') ? path : base + path). It is a latent SSRF: the day any caller passes user-influenced input, the server fetches an attacker URL — internal hosts and cloud metadata included. Keep the internal-base fetcher and the user-URL fetcher as distinct, type-separated clients.

  • Treat "this endpoint is only reachable from our internal / VPN / allowlisted

network" as mitigation for an unsafe server-side fetch. SSRF originates from inside that network, so it reaches exactly the internal services the network control was meant to protect — an IP allowlist is not an authentication boundary against SSRF.

  • Allow IDN / Punycode in user URLs without normalization — IDN homograph

attacks bypass naive string-allowlist checks (gооgle.com Cyrillic-o ≠ google.com).

KNOWN FALSE POSITIVES

  • Server-to-server integrations where both sides are operator-controlled

and the URL is hard-coded in config (not user-supplied) — the allowlist here is the static config itself.

  • Cluster-local Kubernetes service-to-service calls — these don't go

through user input, but be aware of any cross-namespace network policy.

  • Outbound webhooks to the customer (e.g. Slack, Discord, Microsoft

Teams webhooks). Validate that the URL host is in the integration's documented allowlist, not arbitrary.

  • A forwarder that uses a hard-coded absolute URL constant (the base is a

literal, only a query string or path segment is appended from input) does not change host — the SSRF exists only when the absolute URL itself can be influenced by user input.

Context (for humans)

SSRF is now the de-facto initial-access vector for cloud breaches. The chain is: a user-supplied URL → the server fetches it → the server has implicit credentials (cloud metadata IAM, internal admin APIs, RPC endpoints) → attacker steals the credentials. Capital One's 2019 breach (80M customer records) was a textbook SSRF + IMDSv1 exfiltration. The fixes are simple and well-documented; the patterns reappear because URL-fetching is a tiny corner of most codebases.

This skill emphasizes the DNS-rebinding and redirect-bypass classes because those are where AI-generated URL validators most often fail — the obvious 169.254.169.254 block is easy to add, but the allow-only-after-resolve-and-pin pattern requires more thought.

Verify & lock (triaging a finding)

A scanner/review hit is a candidate, not a confirmed bug. Confirm it, fix it, then lock it so it can't come back.

  1. Confirm it's real (probe the suspect input). Point the URL parameter at:

cloud metadata (http://169.254.169.254/latest/meta-data/), an internal service (http://127.0.0.1:6379, http://localhost:22), and a redirect that 302s to an internal IP (rebind/redirect bypass). Real if the server dereferences it — internal data comes back, or your out-of-band listener fires. False positive if the input is already allowlist-constrained or never fetched.

  1. Fix, then lock with a regression test (unit or integration — dev's call):

unit — feed those internal/metadata URLs to the validator and assert rejected, feed an allowlisted URL and assert accepted; integration — POST the internal URL to the endpoint and assert 4xx and that no outbound request to the internal host was made (stub the dialer). Commit it to CI so the guard can't be silently dropped in a later refactor.

References

All versions