browser plugin: add navigation RPCs + bump to 0.1.2#4870
Conversation
33e6bca to
d3c0129
Compare
| @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"}) |
There was a problem hiding this comment.
🔴 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:
returnHowever, 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.
| @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"}) |
Was this helpful? React with 👍 or 👎 to provide feedback.
No description provided.