fix(browser): extend private-network guard to browser_vision - #45101
fix(browser): extend private-network guard to browser_vision#45101liuhao1024 wants to merge 2 commits into
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 NousResearch#44731
The SSRF bypass in NousResearch#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.
egilewski
left a comment
There was a problem hiding this comment.
Recommendation: request changes
I reviewed this in security mode against current GitHub main 46d758bb3e0709bef51b7e3416cfb25da95d2335, PR base 46d758bb3e0709bef51b7e3416cfb25da95d2335, and PR head d1ffb138e354d71536e02551b55b290a7ec62826.
Validation:
git rev-list --left-right --count upstream/main...refs/remotes/upstream/pr/45101:30 2.git merge-tree --write-tree upstream/main refs/remotes/upstream/pr/45101: passed, tree741221bb668648177721fdc3604abc1a3d9e2a49.git diff --check upstream/main...refs/remotes/upstream/pr/45101: passed.python -B -m pytest -q tests/tools/test_browser_snapshot_ssrf.py tests/tools/test_browser_ssrf_local.py tests/tools/test_browser_eval_supervisor_path.py -p no:cacheprovider: passed,52 passed.python -B -m py_compile tools/browser_tool.py tests/tools/test_browser_snapshot_ssrf.py: passed.
Finding:
This still needs rework because the new current-URL guards block the existing hybrid local-sidecar path for ordinary private URLs. Current browser_navigate() intentionally skips the full private-IP block when the navigation is served by a local sidecar, and the existing test says hybrid routing for ordinary private URLs “must be narrow enough to not break the PR #16136 feature” (tests/tools/test_browser_ssrf_local.py). With hybrid routing forced to review::local, current main allows browser_navigate("http://192.168.1.1/admin") and a follow-up browser_snapshot() returns the local sidecar page. On this PR head, the same navigation still succeeds, but the follow-up browser_snapshot() is rejected by the new guard:
{"success": false, "error": "Blocked: page URL targets a private or internal address (http://192.168.1.1/admin)..."}.
The same issue affects browser_vision(): with _last_active_session_key["review"] = "review::local", the PR-head guard calls eval on the sidecar session and blocks before screenshot capture. The guard needs to distinguish the eval-mutated cloud session from the legitimate _is_local_sidecar_key(effective_task_id) case, while preserving the existing always-blocked cloud-metadata floor.
I stopped after this blocker.
Signed: GPT-5.5-xhigh in Codex
|
Superseded by #45133 which addresses the review feedback from @egilewski. The fix adds All 17 SSRF tests pass, including the new |
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 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 NousResearch#45101 review feedback from egilewski.
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 NousResearch#45101 review feedback from egilewski.
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 NousResearch#45101 review feedback from egilewski.
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 NousResearch#45101 review feedback from egilewski.
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 NousResearch#45101 review feedback from egilewski.
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 NousResearch#45101 review feedback from egilewski.
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 NousResearch#45101 review feedback from egilewski.
What does this PR do?
Extends the private-network SSRF guard (from #44755) to
browser_vision()in addition tobrowser_snapshot(). Afterbrowser_console()navigates to a private/internal URL via JavaScript eval,browser_vision()would capture and forward the private page content to the vision model without checking the current URL. This PR adds the same re-check ofwindow.location.hrefbefore any screenshot is taken.This addresses the CHANGES_REQUESTED review by @egilewski on #44755, which found that
browser_vision()remained as a bypass vector for the eval-navigation SSRF attack.Related Issue
Fixes #44731 (extends #44755)
Supersedes #44755
Type of Change
Changes Made
tools/browser_tool.py: Add private-network URL safety guard tobrowser_vision()before any screenshot is captured. Checkswindow.location.hrefvia CDP eval and blocks if the URL targets a private/internal address. Guard is skipped for local backends and whenallow_private_urlsis enabled. Fails open if the eval check itself fails.tests/tools/test_browser_snapshot_ssrf.py: Add 7 tests forbrowser_vision()SSRF guard — blocks private URL, allows public URL, skips in local backend, skips when private URLs allowed, handles eval failure/empty/exception gracefully.How to Test
python -B -m pytest -q tests/tools/test_browser_snapshot_ssrf.py -p no:cacheproviderbrowser_vision()returns{"success": false, "error": "Blocked: page URL targets a private or internal address..."}when the page has been navigated tohttp://127.0.0.1:8080/secretvia eval.Checklist
Code
fix(scope):,feat(scope):, etc.)pytest tests/ -qand all tests passDocumentation & Housekeeping
docs/, docstrings) — or N/Acli-config.yaml.exampleif I added/changed config keys — or N/ACONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — or N/ACode Intelligence
_is_safe_url,_is_local_backend,_allow_private_urls,_run_browser_command(all defined intools/browser_tool.py, 21 references)browser_snapshot()SSRF fix (line 2533)