feat(tools): SSRF guard on browse_url — block loopback / private / link-local targets#58
Merged
Merged
Conversation
…ocal targets
Follow-up to TOOLS_AUDIT_2026-05-25 (paired with security review F-03).
browse_url previously accepted any well-formed http(s) URL, so the model
(or a redirect chain) could be steered at internal resources:
- localhost / 127.0.0.1 — local dev servers, admin panels
- 10.x / 172.16-31.x / 192.168.x — internal corporate / home network
- 169.254.169.254 — AWS/GCP/Azure cloud metadata service (creds leak)
- ::1, fe80::/10, fc00::/7 — IPv6 equivalents
Add assertNotSSRF() called from two places:
1. The validator, so the model gets a clear error before the request.
2. The impl boundary, so redirect re-entry (callTool.browse_url skips
the validator) and any future internal caller can't bypass it.
Covers literal hostname bans only. DNS-resolution bypasses (a
public-looking hostname that resolves to a private IP) are not caught
here — that needs async preflight + IPs-of-redirect checking and is
queued as a follow-up.
Includes unit tests covering loopback, private ranges, link-local
(incl. cloud metadata), IPv6 forms, IPv4-mapped IPv6, and boundary
cases just outside the blocked CIDRs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.
Summary
Closes the SSRF item in TOOLS_AUDIT_2026-05-25 §5 (pairs with security review F-03).
browse_urlpreviously accepted any well-formedhttp(s)://URL — the model (or a redirect chain) could be steered at internal targets that the user never intended to expose to the agent:localhost,127.0.0.110.x,172.16-31.x,192.168.x169.254.169.254::1,fe80::/10,fc00::/7::ffff:127.0.0.1etc.Fix
New
assertNotSSRF(url)helper, called from two sites:validateParams.browse_url) — model gets a clear error before the request fires.callTool.browse_url) — redirect re-entry callsthis.callTool.browse_urldirectly, skipping the validator, so the guard needs to run there too.Unit tests cover loopback, all three private CIDRs, link-local (incl. cloud metadata), IPv6 forms, IPv4-mapped IPv6, and boundary cases just outside the blocked CIDRs (172.15.x / 172.32.x / 192.169.x).
2 files changed, +138 / -0 (helper: 57 lines, wire-in: 2 lines, tests: 79 lines).
Known gap (queued, not in this PR)
DNS-based bypass: a hostname like
evil.example.comthat resolves to127.0.0.1will still be allowed by the literal-only check. Catching that needs:requestService)That's a meaningful chunk of work and benefits from a centralised allowlist/blocklist setting. Tracked as a follow-up; this PR closes the literal-IP class which is the higher-frequency attack vector for prompt-injected URLs.
Test plan
npm run compile-check-ts-nativepassesbrowse_urlwithhttp://127.0.0.1in agent mode — confirm clear error surfaces to the modelhttps://example.comand other public URLs still workAudit follow-ups remaining after this PR
run_nl_command(design call needed)web_searchDDG scraping fragility (long-term — SerpAPI/Brave or graceful failure path)WorkspaceEdit-based rename, Monaco-refactor-based extract, internal-LLM review/tests🤖 Generated with Claude Code