Skip to content

browser plugin: add navigation RPCs + bump to 0.1.2#4870

Merged
theomonnom merged 3 commits intomainfrom
theo/browser-navigation
Feb 17, 2026
Merged

browser plugin: add navigation RPCs + bump to 0.1.2#4870
theomonnom merged 3 commits intomainfrom
theo/browser-navigation

Conversation

@theomonnom
Copy link
Member

No description provided.

@chenghao-mou chenghao-mou requested a review from a team February 17, 2026 03:04
@theomonnom theomonnom force-pushed the theo/browser-navigation branch from 33e6bca to d3c0129 Compare February 17, 2026 03:05
Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 3 additional findings.

Open in Devin Review

@theomonnom theomonnom merged commit baaf655 into main Feb 17, 2026
3 of 16 checks passed
@theomonnom theomonnom deleted the theo/browser-navigation branch February 17, 2026 03:09
Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

View 5 additional findings in Devin Review.

Open in Devin Review

Comment on lines +136 to +158
@self._room.local_participant.register_rpc_method("browser/navigate") # type: ignore[arg-type]
async def _handle_navigate(
data: rtc.rpc.RpcInvocationData,
) -> str:
payload = json.loads(data.request_body)
url = payload.get("url", "")
if url:
self._queue_input(self._page.navigate(url))
return json.dumps({"status": "ok"})

@self._room.local_participant.register_rpc_method("browser/go-back") # type: ignore[arg-type]
async def _handle_go_back(
data: rtc.rpc.RpcInvocationData,
) -> str:
self._queue_input(self._page.go_back())
return json.dumps({"status": "ok"})

@self._room.local_participant.register_rpc_method("browser/go-forward") # type: ignore[arg-type]
async def _handle_go_forward(
data: rtc.rpc.RpcInvocationData,
) -> str:
self._queue_input(self._page.go_forward())
return json.dumps({"status": "ok"})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Navigation RPCs bypass focus-based access control, allowing any participant to navigate the browser

The new browser/navigate, browser/go-back, and browser/go-forward RPC handlers do not verify that data.caller_identity matches self._focus_identity before performing the action. In contrast, the existing input data handler at session.py:167 explicitly checks packet.participant.identity != self._focus_identity and drops input from non-focus-holders.

Root Cause and Impact

The focus mechanism exists to ensure only one authorized participant controls the browser at a time. Keyboard and mouse events are gated by the focus check at line 167:

if packet.participant.identity != self._focus_identity:
    return

However, the navigation RPCs skip this check entirely. For example, _handle_navigate at lines 137-144 immediately queues a navigation without verifying the caller:

async def _handle_navigate(data: rtc.rpc.RpcInvocationData) -> str:
    payload = json.loads(data.request_body)
    url = payload.get("url", "")
    if url:
        self._queue_input(self._page.navigate(url))
    return json.dumps({"status": "ok"})

Impact: Any participant in the room — even one that has not acquired focus — can navigate the browser to an arbitrary URL, or trigger back/forward navigation. This bypasses the focus-based access control model that protects all other browser input, and could be exploited to redirect the browser session unexpectedly.

Suggested change
@self._room.local_participant.register_rpc_method("browser/navigate") # type: ignore[arg-type]
async def _handle_navigate(
data: rtc.rpc.RpcInvocationData,
) -> str:
payload = json.loads(data.request_body)
url = payload.get("url", "")
if url:
self._queue_input(self._page.navigate(url))
return json.dumps({"status": "ok"})
@self._room.local_participant.register_rpc_method("browser/go-back") # type: ignore[arg-type]
async def _handle_go_back(
data: rtc.rpc.RpcInvocationData,
) -> str:
self._queue_input(self._page.go_back())
return json.dumps({"status": "ok"})
@self._room.local_participant.register_rpc_method("browser/go-forward") # type: ignore[arg-type]
async def _handle_go_forward(
data: rtc.rpc.RpcInvocationData,
) -> str:
self._queue_input(self._page.go_forward())
return json.dumps({"status": "ok"})
@self._room.local_participant.register_rpc_method("browser/navigate") # type: ignore[arg-type]
async def _handle_navigate(
data: rtc.rpc.RpcInvocationData,
) -> str:
if self._focus_identity != data.caller_identity:
return json.dumps({"status": "error", "reason": "not focused"})
payload = json.loads(data.request_body)
url = payload.get("url", "")
if url:
self._queue_input(self._page.navigate(url))
return json.dumps({"status": "ok"})
@self._room.local_participant.register_rpc_method("browser/go-back") # type: ignore[arg-type]
async def _handle_go_back(
data: rtc.rpc.RpcInvocationData,
) -> str:
if self._focus_identity != data.caller_identity:
return json.dumps({"status": "error", "reason": "not focused"})
self._queue_input(self._page.go_back())
return json.dumps({"status": "ok"})
@self._room.local_participant.register_rpc_method("browser/go-forward") # type: ignore[arg-type]
async def _handle_go_forward(
data: rtc.rpc.RpcInvocationData,
) -> str:
if self._focus_identity != data.caller_identity:
return json.dumps({"status": "error", "reason": "not focused"})
self._queue_input(self._page.go_forward())
return json.dumps({"status": "ok"})
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

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.

1 participant