Skip to content

fix: validate every redirect hop before fetching InputFile URLs - #124

Draft
abnegate wants to merge 1 commit into
mainfrom
cursor/fix-inputfile-redirect-ssrf-26a7
Draft

abnegate wants to merge 1 commit into
mainfrom
cursor/fix-inputfile-redirect-ssrf-26a7

Conversation

@abnegate

@abnegate abnegate commented Oct 5, 2026

Copy link
Copy Markdown
Member

Problem

Hosted InputFile URL fetches (_fetch_input_file) validated the caller-supplied URL, then let httpx follow redirects automatically and only re-checked resp.url after the final request had already been sent. A public host that answered 302 Location: http://169.254.169.254/... (or loopback / RFC1918) made the server issue a GET to that internal address. The post-fetch check discarded the body, but the request had already happened.

Fetch failures were also an oracle: a blocked internal hop, an HTTP error, and a connection failure each produced different caller-visible text, and httpx errors included the final URL and status code.

Fix

  • Disable automatic redirects and follow Location manually up to the existing FETCH_MAX_REDIRECTS cap.
  • Resolve and validate every hop (initial URL and each Location, including relative ones via urljoin) before opening a connection to that hop.
  • Pin TCP to the IP just validated and keep the original hostname as Host and TLS sni_hostname, so HTTPS certificate verification is unchanged and the resolve-then-reconnect DNS-rebinding window is closed.
  • Preserve the existing size cap, timeout, redirect cap, and filename derivation for legitimate public URLs.
  • Map redirect / HTTP / connection failures to a single generic caller-visible message. Specific validation errors remain only for the caller-supplied URL (scheme, DNS, private). Details stay in server logs.

Tests

Unit coverage in tests/unit/test_server.py:

  • Public → loopback / private / link-local redirect is refused and a local internal listener receives no request
  • Direct public URL and public → public redirect still work (mocked client and local HTTP servers)
  • Relative Location is joined against the logical hostname, not the pinned IP
  • Redirect cap still enforced
  • HTTP errors, connection errors, and blocked redirect hops all return the same generic message
  • DNS / getaddrinfo is mocked; tests do not use the public internet

Checks

ruff, black --check, pyright, and python -m unittest discover -s tests/unit pass locally.

Open in Web Open in Cursor 

Follow InputFile URL redirects manually and resolve/validate each hop
before connecting so a public host cannot bounce the hosted server onto
loopback, private, or link-local addresses. Pin TCP to the validated IP
while keeping Host and TLS SNI on the original hostname. Return a
generic fetch error to callers so redirect failures are not an SSRF
oracle.

Co-authored-by: Jake Barnby <abnegate@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants