fix(browser): seal eval-path SSRF bypass + land snapshot/vision guards (#44731) - #54132
Merged
Conversation
browser_snapshot() now checks the current page URL before returning content. When browser_console() changes location.href to a private or internal address (e.g., http://127.0.0.1:8080/), the snapshot returns an error instead of exposing the private page content. This closes the SSRF bypass where an attacker could: 1. Navigate to a public page 2. Use browser_console to eval location.href = 'http://127.0.0.1:port/' 3. Use browser_snapshot to read the private page content The fix reuses the existing _is_safe_url() and _allow_private_urls() infrastructure, and fails open if the URL check itself fails. Fixes #44731
The SSRF bypass in #44731 was only patched for browser_snapshot(), but browser_vision() exposes the same vulnerability — it takes a screenshot and sends it to the vision model without checking if eval-driven navigation moved the page to a private/internal URL. Add the same current-page URL safety check to browser_vision() before any screenshot is captured, encoded, or forwarded to the vision model. This covers both the normal screenshot path and the Lightpanda Chrome fallback path. 7 new tests: blocks private URL, allows public URL, skips in local backend, skips when private URLs allowed, handles eval failure/empty/exception.
The private-network guard in browser_snapshot() and browser_vision() blocked all private URLs, including those accessed via local sidecar sessions (hybrid routing). Local sidecar sessions intentionally access private URLs — the cloud provider never sees the URL in that case. Add `_is_local_sidecar_key(effective_task_id)` check to both guards, matching the existing pattern in browser_navigate(). Fixes #45101 review feedback from egilewski.
The snapshot/vision guards re-check the page URL before returning content,
but browser_console(expression=...) -> _browser_eval returns arbitrary JS
results directly, leaving two same-class bypasses open:
1. Direct fetch: fetch('http://127.0.0.1/secret').then(r=>r.text()) reads
a private endpoint and returns the body — the page URL stays public so
the post-eval recheck never sees it.
2. Navigate-then-read: location.href='http://127.0.0.1/' then a later eval
reads document.body.innerText.
Guard _browser_eval on the same condition as navigate/snapshot/vision
(not local backend, not local sidecar, not allow_private_urls):
- pre-scan the expression for private/always-blocked URL literals
- re-check window.location.href after the eval at both success-return
sites (supervisor fast-path + subprocess fallback)
Probe failures fail-open (matching the snapshot/vision guards).
Contributor
🔎 Lint report:
|
| Rule | Count |
|---|---|
unresolved-import |
2 |
First entries
tests/tools/test_browser_eval_ssrf.py:19: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/tools/test_browser_snapshot_ssrf.py:12: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
✅ Fixed issues: none
Unchanged: 6104 pre-existing issues carried over.
Diagnostics are surfaced as warnings — this check never fails the build.
This was referenced Jun 28, 2026
3 tasks
This was referenced Jul 1, 2026
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 eval return-value SSRF bypass from #44731 and lands the contributor's snapshot/vision guards, so the browser's private-network policy holds across all four content sinks instead of just navigation.
Root cause:
browser_navigateblocks private/internal URLs in cloud-browser mode (and containerized-terminal mode), but page-context JS can re-route the main frame —browser_console(expression="location.href='http://127.0.0.1/'")thenbrowser_snapshot()reads the private page back. The guard was only on the navigate path.Changes
tools/browser_tool.pywindow.location.hrefagainst the SSRF guard before returning content, on the same gate as navigate (not local backend · not local sidecar · not allow_private_urls)._browser_evalreturned arbitrary JS results with no URL check — two same-class bypasses stayed open. Now guarded:fetch('http://127.0.0.1/secret')never updateslocation.href, so the page recheck can't see it).window.location.hrefafter the eval at both success-return sites (supervisor fast-path + subprocess fallback).169.254.169.254) covered via_is_always_blocked_urlin both new guards.tests/tools/test_browser_snapshot_ssrf.py(@liuhao1024) — 17 tests, snapshot + vision guards.tests/tools/test_browser_eval_ssrf.py— 12 tests, both eval sub-paths + skip cases (local backend / sidecar / allow_private) + helper units.Validation
browser_navigate→ privatebrowser_snapshotafter eval-nav → privatebrowser_visionafter eval-nav → privatebrowser_console(fetch private)browser_console(location.href=private)+ read DOME2E with real
is_safe_url(real DNS/loopback resolution, no mocks): both attack paths blocked, canary never leaks, legit public eval still works. 29/29 new tests + 434/434 existing browser tests green.Credit
Snapshot/vision guards cherry-picked from @liuhao1024's #45133 (supersedes #44755, #45101) with authorship preserved. Eval sibling fix added on top. Reporter: @YLChen-007 (#44731).
Infographic