security: web_search follows redirects without re-checking SSRF
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
- Call
WebSearchTool.execute(query="https://public.example.com/start"). - Make the public URL return
302 Location: http://127.0.0.1/admin. - Make the loopback URL return an HTML response.
- 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
mainat56cd19a - 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.
Source: open-jarvis/OpenJarvis