Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions tests/tools/test_browser_cdp_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,70 @@ async def fake_call(*args, **kwargs):
assert calls == []


def test_frame_id_route_blocked_when_current_page_is_private(monkeypatch):
"""frame_id routing (OOPIF via supervisor) must not bypass the guard
applied to the stateless path — same private-page boundary either way."""
supervisor_calls = []

import tools.browser_tool as bt

monkeypatch.setattr(bt, "_eval_ssrf_guard_active", lambda task_id: True)
monkeypatch.setattr(bt, "_current_page_private_url", lambda task_id: PRIVATE_URL)

def fake_supervisor_route(**kwargs):
supervisor_calls.append(kwargs)
return json.dumps({"success": True, "result": {"value": "private data"}})

monkeypatch.setattr(
browser_cdp_tool, "_browser_cdp_via_supervisor", fake_supervisor_route
)

result = json.loads(
browser_cdp_tool.browser_cdp(
method="Runtime.evaluate",
params={"expression": "document.body.innerText"},
frame_id="frame-1",
task_id="task-1",
)
)

assert "error" in result
assert PRIVATE_URL in result["error"]
assert "private or internal address" in result["error"]
assert supervisor_calls == []


def test_frame_id_route_allowed_when_page_is_not_private(monkeypatch):
"""Sanity check: the new guard call must not block ordinary frame_id
routing when the current page isn't private."""
supervisor_calls = []

import tools.browser_tool as bt

monkeypatch.setattr(bt, "_eval_ssrf_guard_active", lambda task_id: True)
monkeypatch.setattr(bt, "_current_page_private_url", lambda task_id: None)

def fake_supervisor_route(**kwargs):
supervisor_calls.append(kwargs)
return json.dumps({"success": True, "result": {"value": "ok"}})

monkeypatch.setattr(
browser_cdp_tool, "_browser_cdp_via_supervisor", fake_supervisor_route
)

result = json.loads(
browser_cdp_tool.browser_cdp(
method="Runtime.evaluate",
params={"expression": "document.title"},
frame_id="frame-1",
task_id="task-1",
)
)

assert result.get("success") is True
assert len(supervisor_calls) == 1


def test_page_navigate_to_private_url_blocked_before_cdp(monkeypatch):
calls = []

Expand Down
9 changes: 9 additions & 0 deletions tools/browser_cdp_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,15 @@ def browser_cdp(

# --- Route iframe-scoped calls through the supervisor ---------------
if frame_id:
# Same private-page/SSRF boundary as the stateless path below —
# frame_id routing must not become the sibling bypass for it.
blocked = _browser_cdp_private_guard(
task_id=effective_task_id,
method=method,
params=params or {},
)
if blocked:
return blocked
return _browser_cdp_via_supervisor(
task_id=effective_task_id,
frame_id=frame_id,
Expand Down
Loading