Skip to content
Hidden Logic Lab

Review outbound URL fetchers for SSRF before merge

Cursor or Claude Code applies a SKILL.md on PRs that fetch a caller-supplied URL: private-network targets, redirect hops to metadata endpoints, and DNS rebinding — with merge-blocking findings.

·Coding·40 min·Deep

An ssrf-review SKILL.md linked from AGENTS.md, a PR comment mapping each fetch site to its allowlist, and no new fetcher merged that can reach link-local or cloud metadata addresses.

  1. 1

    Inventory fetch sites before the first review

    List unfurl, proxy, webhook, and import jobs in the skill appendix. A review that only reads route handlers misses the worker.

  2. 2

    Run the skill on a URL-shaped PR

    Use a diff that adds or edits a client. The comment should name the allowlist or mark it missing.

  3. 3

    Link it next to webhook reviews

    Point the PR template for outbound HTTP at this SKILL.md. Idempotency review does not catch a metadata hop.

The prompt to paste

Write SKILL.md for reviewing outbound URL fetchers before merge. Focus on server-side request forgery: the server connects somewhere the caller chose. This is not a general HTTP-client style review.

Repository context:
[fetch sites: link unfurl, image proxy, webhook delivery, import-from-URL, PDF renderer, oEmbed — name the ones this repo has]
[allowlist policy: exact hosts, suffix rules, or deny-by-default — paste the current list or mark none]
[cloud: AWS, GCP, Azure, or none — so metadata hostnames are concrete]
[HTTP client library names already in the repo]

PR context (paste when using):
[PR link, changed files, which new or edited function performs the outbound request]

SKILL.md must include:
1. Purpose — a fetcher that follows a user URL can read cloud metadata, internal admin panels, and neighboring tenants' services.
2. When to use — any PR that builds a URL from request input, stored user content, or a webhook target and then connects. When not to use — uploads of bytes already in the request body (that is the file-upload review), or CSP header-only diffs.
3. Hard rules for findings:
   - Block: connect without an allowlist; schemes other than https (file, gopher, dict, ftp); redirects followed without re-checking the target; resolution of hostnames that map to loopback, link-local, RFC1918, unique-local IPv6, 0.0.0.0, or cloud metadata (169.254.169.254, metadata.google.internal, and the provider host your context names); DNS rebinding where the IP checked at resolve time is not the IP used at connect time; disabled TLS verification to "make staging work."
   - Warn: allowlist implemented as endsWith on a suffix the attacker can register (evil-allowed.com vs allowed.com); missing connect and total timeouts; response bodies written to logs; HTTP (not HTTPS) even to an allowlisted host; IPv6 literals skipped by an IPv4-only denylist.
   - Note: User-Agent string and retry counts — never block merge on those alone.
4. Review steps the agent must execute:
   - List every call site that opens a socket from non-constant input. Include queue workers, not only route handlers.
   - For each site, record: who supplies the URL, schemes accepted, redirect policy, and where the allowlist lives.
   - Trace redirect handling: a 30x to a metadata IP after an allowlisted first hop is a Block.
   - Check whether DNS lookup and TCP connect share one pinned address. Flag check-then-connect gaps.
   - Confirm error paths do not return the upstream body or internal hostname to the caller.
5. PR comment template: fetch-site table (function — input source — allowlist — redirect policy — verdict), findings by severity, merge recommendation.
6. Safe patterns section: deny-by-default host allowlist, https only, no redirects or re-validate each hop, connect to the resolved public IP you just checked, short timeouts.
7. Two example prompts: add an oEmbed unfurl endpoint vs change a timeout on an already allowlisted partner webhook.
8. Pitfalls: testing only with https://example.com, blocking 127.0.0.1 in a regex but allowing decimal or IPv6 forms, trusting a URL parser that normalizes after your check, treating "we are not on AWS" as a reason to skip metadata hosts.
9. Done means: every Block resolved or the PR rejected, each fetch site has a named allowlist or an explicit "no user URL" note, no internal hostnames pasted from a live exploit attempt.

Rules:
- Do not invent fetch sites that are not in the diff or the repository context.
- Do not include working exploit payloads. Name the address class and the code path.
- Output only SKILL.md markdown.

An allowlisted first hop that follows redirects still reaches metadata addresses. The skill must Block redirect-following unless every hop is rechecked against the same denylist.

Sample case written by Hidden Logic Lab. Not affiliated with Cursor or Claude Code.

More in Coding

Get new cases in your inbox

Weekly notes on new use cases and agent workflows. No product pitches.

Occasional notes on new cases. No product pitches in the first note.