security: web_search follows redirects without re-checking SSRF

Author: Hao-tian-ZhengCreated Sep 5, 2026Updated Sep 5, 2026

Description

web_search checks a user-supplied URL before fetching it, but _fetch_url() calls httpx.get(..., follow_redirects=True) and does not validate each redirect target. A public URL can therefore redirect the fetcher to a loopback, private-network, or cloud-metadata address.

Steps to Reproduce

  1. Call WebSearchTool.execute(query="https://public.example.com/start").
  2. Make the public URL return 302 Location: http://127.0.0.1/admin.
  3. Make the loopback URL return an HTML response.
  4. Observe that the response is returned as a successful search result while the SSRF guard only sees the original public URL.

The issue reproduces deterministically with an httpx.MockTransport: the request sequence is https://public.example.com/start then http://127.0.0.1/admin, while check_ssrf is called only for https://public.example.com/start.

Expected Behavior

Every redirect hop is checked by check_ssrf before the request is sent. A redirect to a private or metadata address returns a failed tool result and no internal response is exposed.

Actual Behavior

The redirect is followed by HTTPX without a second SSRF check, and the final internal response is returned successfully.

Environment

  • OpenJarvis main at 56cd19a
  • Python 3.13

Suggested Direction

Reuse the existing manual redirect pattern in HttpRequestTool, or add an equivalent checked redirect transport for _fetch_url(). Add a regression test covering a public URL redirecting to loopback and preserve relative/public redirects.