Skip to content

security: vet every browser navigation, not just the model-supplied URL (SSRF) - #564

Open
coderdailyone wants to merge 1 commit into
andrewyng:mainfrom
coderdailyone:security/browser-guard-every-navigation
Open

security: vet every browser navigation, not just the model-supplied URL (SSRF)#564
coderdailyone wants to merge 1 commit into
andrewyng:mainfrom
coderdailyone:security/browser-guard-every-navigation

Conversation

@coderdailyone

Copy link
Copy Markdown

Fixes #524.

browser_open_url checks the initial URL with check_url once, then hands navigation to Playwright — which follows redirects, runs page JS, and re-resolves DNS itself. redirect_refusal (OPE-124) re-checks only the single goto's final URL after the fact; a later navigation — a 30x hop, a meta-refresh, location = … — to 127.0.0.1 / 169.254.169.254 is never re-vetted before browser_read_page / browser_screenshot lift its content into the agent context. browser_read_page applies no page.url check at all, so the JS-navigation path is fully readable.

Change

  • Install a context-level Playwright route interceptor (_guard_route) that runs the same address guard (check_url, as web_fetch uses) on every navigation request and route.abort("blockedbyclient")s the blocked ones before the request goes out. This covers redirects, JS- and meta-refresh-driven navigation, and narrows the DNS-rebinding TOCTOU (the handler sees the concrete request about to fire). Fails closed if the guard errors or the request shape is unexpected.
  • Subresource fetches (images/scripts/XHR) are intentionally not gated — they can't read internal content back into the agent, and vetting each would resolve DNS dozens of times per page. The read-back path is what's closed here; a resolver-pinning proxy remains the larger design for blind subresource SSRF (noted in the code).
  • redirect_refusal stays as complementary defense in depth (nice error messaging for the goto case).

Tests

New tests/test_browser_url_guard.py exercises _guard_route directly (no Playwright needed) with fake Route/Request objects, reusing the address-guard suite's DNS-monkeypatch pattern:

  • navigations to loopback/metadata literals and to hostnames resolving to loopback/metadata are aborted;
  • an allowed public navigation continues;
  • a subresource request to a loopback literal is not gated (read-back scope);
  • the guard fails closed when is_navigation_request() raises.

All pass; existing test_browser_root_scoping and test_url_address_guard suites stay green (64 total).

🤖 Generated with Claude Code

https://claude.ai/code/session_01CLxsdFGXjdztTRNHjNgPXP

browser_open_url checked the initial URL with check_url once, then handed
navigation to Playwright, which follows redirects, runs page JS, and
re-resolves DNS itself. redirect_refusal (OPE-124) re-checks only the
single goto's final URL after the fact; a later navigation — a 30x hop,
a meta-refresh, location=… — to 127.0.0.1 / 169.254.169.254 was never
re-vetted before browser_read_page / browser_screenshot lifted its
content into the agent context. browser_read_page in particular applies
no page.url check at all.

Fix: install a context-level Playwright route interceptor that runs the
same address guard (check_url) on every NAVIGATION request and aborts the
blocked ones before the request goes out. This covers redirects, JS- and
meta-refresh-driven navigation, and narrows the DNS-rebinding window
(the handler sees the concrete request). Subresource fetches are not
gated (they can't read internal content back, and vetting each would
resolve DNS dozens of times per page) — the read-back path is closed;
a resolver-pinning proxy remains the larger design for blind SSRF.

Fixes andrewyng#524

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CLxsdFGXjdztTRNHjNgPXP
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.

Security: SSRF in browser_open_url — redirects and DNS rebinding bypass the address guard (initial-URL check only)

1 participant