From 603321416d12a387feac92e484dc6a28c93ffcef Mon Sep 17 00:00:00 2001 From: Alexandre Drouin Date: Tue, 3 Feb 2026 10:24:08 -0500 Subject: [PATCH] Hide context menus on form and list pages to prevent exploits The right-click context menus on form headers and list rows provided options that could be used to modify fields/data outside the task scope. This adds MutationObservers that remove context menus as soon as they appear, preventing agents from using this mechanism to bypass restrictions. Bump version to 0.5.3. Co-Authored-By: Claude Opus 4.5 --- src/browsergym/workarena/__init__.py | 2 +- src/browsergym/workarena/tasks/form.py | 24 +++++++++++++++++++ src/browsergym/workarena/tasks/list.py | 32 ++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 1 deletion(-) diff --git a/src/browsergym/workarena/__init__.py b/src/browsergym/workarena/__init__.py index 04dac6d..afeafe1 100644 --- a/src/browsergym/workarena/__init__.py +++ b/src/browsergym/workarena/__init__.py @@ -1,4 +1,4 @@ -__version__ = "0.5.2" +__version__ = "0.5.3" # Check playwright version early to avoid cryptic errors import importlib.metadata diff --git a/src/browsergym/workarena/tasks/form.py b/src/browsergym/workarena/tasks/form.py index 742dc0e..c32e83d 100644 --- a/src/browsergym/workarena/tasks/form.py +++ b/src/browsergym/workarena/tasks/form.py @@ -395,6 +395,30 @@ def get_init_scripts(self) -> List[str]: runInGsftMainOnlyAndProtectByURL(removeAdditionalActionsButton, '{url_suffix}'); """, + f""" + function removeContextMenus() {{ + waLog('Setting up context menu removal observer...', 'removeContextMenus'); + // Remove any existing context menus + document.querySelectorAll('.context_menu').forEach((menu) => {{ + menu.remove(); + }}); + // Observe for new context menus being added + const observer = new MutationObserver((mutations) => {{ + mutations.forEach((mutation) => {{ + mutation.addedNodes.forEach((node) => {{ + if (node.nodeType === 1 && node.classList && node.classList.contains('context_menu')) {{ + node.remove(); + waLog('Removed dynamically added context menu', 'removeContextMenus'); + }} + }}); + }}); + }}); + observer.observe(document.body, {{ childList: true, subtree: true }}); + waLog('Context menu observer active', 'removeContextMenus'); + }} + + runInGsftMainOnlyAndProtectByURL(removeContextMenus, '{url_suffix}'); + """, ] def start(self, page: Page) -> None: diff --git a/src/browsergym/workarena/tasks/list.py b/src/browsergym/workarena/tasks/list.py index 0f67f42..6c01418 100644 --- a/src/browsergym/workarena/tasks/list.py +++ b/src/browsergym/workarena/tasks/list.py @@ -116,6 +116,7 @@ def get_init_scripts(self) -> List[str]: return super().get_init_scripts() + [ "registerGsftMainLoaded();", self._get_remove_personalize_list_button_script(), + self._get_remove_context_menus_script(), ] def _get_remove_personalize_list_button_script(self): @@ -136,6 +137,37 @@ def _get_remove_personalize_list_button_script(self): """ return script + def _get_remove_context_menus_script(self): + """ + Removes context menus that appear on right-click in list views. + These menus provide options that could be used to modify list data outside the task scope. + """ + script = """ + function removeContextMenus() { + waLog('Setting up context menu removal observer...', 'removeContextMenus'); + // Remove any existing context menus + document.querySelectorAll('.context_menu').forEach((menu) => { + menu.remove(); + }); + // Observe for new context menus being added + const observer = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + mutation.addedNodes.forEach((node) => { + if (node.nodeType === 1 && node.classList && node.classList.contains('context_menu')) { + node.remove(); + waLog('Removed dynamically added context menu', 'removeContextMenus'); + } + }); + }); + }); + observer.observe(document.body, { childList: true, subtree: true }); + waLog('Context menu observer active', 'removeContextMenus'); + } + + runInGsftMainOnlyAndProtectByURL(removeContextMenus, '_list.do'); + """ + return script + def _get_visible_list(self, page: Page): self._wait_for_ready(page)