[eric] web: guard WebFetch against SSRF (scheme + per-redirect private-IP block)#57
Closed
ciregenz wants to merge 1 commit into
Closed
[eric] web: guard WebFetch against SSRF (scheme + per-redirect private-IP block)#57ciregenz wants to merge 1 commit into
ciregenz wants to merge 1 commit into
Conversation
…rect-hop private-IP block)
Collaborator
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #47
What
WebFetchis the agent's built-in URL fetcher. The agent chooses which URL to hit, and that choice can be steered by content on pages it already read (prompt injection). The old guard was scheme-only (in the MCP shim) andfollow_redirects=True, sohttp://169.254.169.254/,http://192.168.x.x/, andhttp://127.0.0.1:8324/all sailed through, and even a public URL could 30x-pivot into the LAN.Fix
New
backend/apps/agents/tools/url_guard.py:validate_fetch_url: rejects non-http(s) schemes; rejects any host that is (or resolves to) a private, loopback, link-local, reserved, multicast, unspecified, or otherwise non-global IP. IP literals are checked as-is so a DNS-rebind literal can't slip past.fetch_guarded: follows redirects manually (httpxfollow_redirects=False) and re-validates every hop before the request, closing the redirect-pivot hole.WebFetchTool.executenow routes throughfetch_guardedand returns a clean "Refused to fetch ..." tool message on a block. This is the single chokepoint both the agent path and the REST/api/web/fetchlocal fallback funnel through. Provider-grounded fetches (Gemini/OpenAI url-context) run on the provider's servers, not the user's LAN, so they aren't in scope.Verify
backend/tests/test_url_guard.py(schemes, IPv4/IPv6 internal literals, cloud-metadata, redirect-to-internal blocked before the request fires, redirect-loop cap). All green.WebFetchTool.executerefuses metadata/loopback/file://; public fetch ofexample.comand an http->https redirect (github.com) still work with content extraction intact.verify-all.jsis the packaged-DMG verifier (boot/signing/python-health); it doesn't exercise WebFetch and needs a built app, so it's N/A for this backend-logic change. Verified via pytest instead.🤖 Generated with Claude Code