fix(browser): block snapshot from eval-navigated private pages - #44755
fix(browser): block snapshot from eval-navigated private pages#44755liuhao1024 wants to merge 1 commit 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
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 906bee9cf7917326bc41d2df559647ec14c4ee7d, and PR head 36f791de89281e95cf693b33d7c54c78d175bfd5.
Validation:
git rev-list --left-right --count upstream/main...refs/remotes/upstream/pr/44755:30 1.git merge-tree --write-tree upstream/main refs/remotes/upstream/pr/44755: passed, tree5c10b0836897a120824fbcf1e3e8e84bdd480952.git diff --check upstream/main...refs/remotes/upstream/pr/44755: 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,46 passed.python -B -m py_compile tools/browser_tool.py tests/tools/test_browser_snapshot_ssrf.py: passed.- Synthetic current-main probe: mocked an active browser page at
http://127.0.0.1:8080/secret;browser_snapshot()returned{"success": true, "snapshot": "PRIVATE_SECRET_FROM_127001"}. - Same synthetic PR-head probe:
browser_snapshot()returned{"success": false, "error": "Blocked: page URL targets a private or internal address ..."}.
Finding:
The fix is still incomplete because it protects only browser_snapshot(). After the same eval-driven private-page navigation, browser_vision() remains a browser content sink: it calls _run_browser_command(..., "screenshot", ...), reads the screenshot, and either attaches it to the active model or sends it to the vision model without re-checking window.location.href first (tools/browser_tool.py around the screenshot and call_llm path). A PR-head synthetic probe with _is_local_backend() == False, _allow_private_urls() == False, and the current page URL mocked as http://127.0.0.1:8080/secret executed only screenshot and returned {"success": true, "analysis": "PRIVATE_SECRET_FROM_127001", ...}. That leaves the same eval-navigation private-network policy bypass available by calling browser_vision() instead of browser_snapshot(), so the security invariant is not fully restored.
Please add the same current-page private/internal URL guard before any browser_vision() screenshot is captured, encoded, attached, or sent to an auxiliary vision model, including the Lightpanda Chrome fallback path.
Signed: GPT-5.5-xhigh in Codex
|
Superseded by #45101, which adds the same private-network guard to |
What does this PR do?
Adds a private-network URL check in
browser_snapshot()to block content exposure whenbrowser_console()has navigated the page to a private/internal address via JavaScript eval.Related Issue
Fixes #44731
Type of Change
Changes Made
tools/browser_tool.py: Added URL safety check inbrowser_snapshot()that evaluateswindow.location.hrefafter getting the snapshot and blocks if the URL targets a private/internal address (30 lines)tests/tools/test_browser_snapshot_ssrf.py: Added 9 tests covering the new private-network guard (248 lines)How to Test
pytest tests/tools/test_browser_snapshot_ssrf.py -vpytest tests/tools/test_browser_ssrf_local.py -vpytest tests/tools/test_browser_eval_supervisor_path.py -vChecklist
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
tools/browser_tool.py::browser_snapshot(callers: N/A, flows: snapshot → eval → URL check → block/allow)browser_snapshot()in cloud mode, no changes to navigation or eval behavior_is_safe_url(),_allow_private_urls(),_is_local_backend()infrastructure fromtools/url_safety.py