Skip to content

Commit 613f0bb

Browse files
authored
Merge pull request #149 from ServiceNow/fix-policy-exploits
Fix policy exploits and add upgrade enforcement mechanism
2 parents e299773 + 066daf5 commit 613f0bb

6 files changed

Lines changed: 128 additions & 6 deletions

File tree

src/browsergym/workarena/__init__.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,14 @@
1-
__version__ = "0.5.1"
1+
__version__ = "0.5.2"
2+
3+
# Check playwright version early to avoid cryptic errors
4+
import importlib.metadata
5+
6+
_playwright_version = importlib.metadata.version("playwright")
7+
if _playwright_version != "1.44.0":
8+
raise RuntimeError(
9+
f"browsergym-workarena requires playwright==1.44.0, but found {_playwright_version}. "
10+
f"Please install the correct version: pip install playwright==1.44.0"
11+
)
212

313
import inspect
414
from logging import warning

src/browsergym/workarena/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
# Hugging Face dataset containing available instances
1515
INSTANCE_REPO_ID = "ServiceNow/WorkArena-Instances"
16-
INSTANCE_REPO_FILENAME = "instances.json"
16+
INSTANCE_REPO_FILENAME = "instances_v2.json"
1717
INSTANCE_REPO_TYPE = "dataset"
1818
INSTANCE_XOR_SEED = "x3!+-9mi#nhlo%a02$9hna{]"
1919

src/browsergym/workarena/instance.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,18 @@ def encrypt_instance_password(password: str) -> str:
4545
return base64.b64encode(cipher_bytes).decode("utf-8")
4646

4747

48-
def fetch_instances():
48+
def fetch_instances(filename: str = None):
4949
"""
5050
Load the latest instances from either a custom pool (SNOW_INSTANCE_POOL env var) or the gated HF dataset.
51+
52+
Parameters:
53+
-----------
54+
filename: str
55+
Optional filename to fetch from the HF dataset. Defaults to INSTANCE_REPO_FILENAME.
5156
"""
57+
if filename is None:
58+
filename = INSTANCE_REPO_FILENAME
59+
5260
pool_path = os.getenv("SNOW_INSTANCE_POOL")
5361
if pool_path:
5462
path = os.path.expanduser(pool_path)
@@ -62,13 +70,13 @@ def fetch_instances():
6270
disable_progress_bars()
6371
path = hf_hub_download(
6472
repo_id=INSTANCE_REPO_ID,
65-
filename=INSTANCE_REPO_FILENAME,
73+
filename=filename,
6674
repo_type=INSTANCE_REPO_TYPE,
6775
)
6876
logging.info("Loaded ServiceNow instances from the default instance pool.")
6977
except Exception as e:
7078
raise RuntimeError(
71-
f"Could not access {INSTANCE_REPO_ID}/{INSTANCE_REPO_FILENAME}. "
79+
f"Could not access {INSTANCE_REPO_ID}/{filename}. "
7280
"Make sure you have been granted access to the gated repo and that you are "
7381
"authenticated (run `huggingface-cli login` or set HUGGING_FACE_HUB_TOKEN)."
7482
) from e
@@ -77,6 +85,8 @@ def fetch_instances():
7785
entries = json.load(f)
7886

7987
for entry in entries:
88+
if entry.get("error"):
89+
raise RuntimeError(entry.get("message", "Unknown error from instance pool"))
8090
entry["url"] = entry["u"]
8191
entry["password"] = decrypt_instance_password(entry["p"])
8292
del entry["u"]

src/browsergym/workarena/tasks/form.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,30 @@ def get_init_scripts(self) -> List[str]:
371371
372372
runInGsftMainOnlyAndProtectByURL(monitorChangeOnFields, '{url_suffix}');
373373
""",
374+
f"""
375+
function removePersonalizeFormButton() {{
376+
waLog('Searching for Personalize Form button...', 'removePersonalizeFormButton');
377+
let button = document.querySelector('#togglePersonalizeForm');
378+
if (button) {{
379+
button.remove();
380+
waLog('Removed Personalize Form button', 'removePersonalizeFormButton');
381+
}}
382+
}}
383+
384+
runInGsftMainOnlyAndProtectByURL(removePersonalizeFormButton, '{url_suffix}');
385+
""",
386+
f"""
387+
function removeAdditionalActionsButton() {{
388+
waLog('Searching for Additional Actions button...', 'removeAdditionalActionsButton');
389+
let button = document.querySelector('button.additional-actions-context-menu-button');
390+
if (button) {{
391+
button.remove();
392+
waLog('Removed Additional Actions button', 'removeAdditionalActionsButton');
393+
}}
394+
}}
395+
396+
runInGsftMainOnlyAndProtectByURL(removeAdditionalActionsButton, '{url_suffix}');
397+
""",
374398
]
375399

376400
def start(self, page: Page) -> None:

src/browsergym/workarena/tasks/list.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,28 @@ def all_configs(cls) -> List[dict]:
113113
return json.load(f)
114114

115115
def get_init_scripts(self) -> List[str]:
116-
return super().get_init_scripts() + ["registerGsftMainLoaded();"]
116+
return super().get_init_scripts() + [
117+
"registerGsftMainLoaded();",
118+
self._get_remove_personalize_list_button_script(),
119+
]
120+
121+
def _get_remove_personalize_list_button_script(self):
122+
"""
123+
Removes the 'Personalize List' button on list pages.
124+
"""
125+
script = """
126+
function removePersonalizeListButton() {
127+
waLog('Searching for Personalize List buttons...', 'removePersonalizeListButton');
128+
let buttons = document.querySelectorAll('i[data-type="list_mechanic2_open"]');
129+
buttons.forEach((button) => {
130+
button.remove();
131+
});
132+
waLog('Removed ' + buttons.length + ' Personalize List buttons', 'removePersonalizeListButton');
133+
}
134+
135+
runInGsftMainOnlyAndProtectByURL(removePersonalizeListButton, '_list.do');
136+
"""
137+
return script
117138

118139
def _get_visible_list(self, page: Page):
119140
self._wait_for_ready(page)

src/browsergym/workarena/tasks/service_catalog.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,9 @@ def get_init_scripts(self) -> List[str]:
225225
"registerGsftMainLoaded()",
226226
self._get_disable_add_to_cart_script(),
227227
self._get_remove_top_items_panel_script(),
228+
self._get_remove_add_content_button_script(),
229+
self._get_remove_header_decorations_script(),
230+
self._get_remove_more_options_buttons_script(),
228231
]
229232

230233
def _get_disable_add_to_cart_script(self):
@@ -276,6 +279,60 @@ def _get_remove_top_items_panel_script(self):
276279
"""
277280
return script
278281

282+
def _get_remove_add_content_button_script(self):
283+
"""
284+
Removes the 'Add content' button from the service catalog page.
285+
"""
286+
script = """
287+
function removeAddContentButton() {
288+
waLog('Searching for Add content button...', 'removeAddContentButton');
289+
let button = document.querySelector('button[aria-label="Add content"]');
290+
if (button) {
291+
button.remove();
292+
waLog('Removed Add content button', 'removeAddContentButton');
293+
}
294+
}
295+
296+
runInGsftMainOnlyAndProtectByURL(removeAddContentButton, 'catalog_home');
297+
"""
298+
return script
299+
300+
def _get_remove_header_decorations_script(self):
301+
"""
302+
Removes all header decoration panels (edit/settings/close buttons) from the service catalog page.
303+
"""
304+
script = """
305+
function removeHeaderDecorations() {
306+
waLog('Searching for header decoration panels...', 'removeHeaderDecorations');
307+
let panels = document.querySelectorAll('div.header_decorations');
308+
panels.forEach((panel) => {
309+
panel.remove();
310+
});
311+
waLog('Removed ' + panels.length + ' header decoration panels', 'removeHeaderDecorations');
312+
}
313+
314+
runInGsftMainOnlyAndProtectByURL(removeHeaderDecorations, 'catalog_home');
315+
"""
316+
return script
317+
318+
def _get_remove_more_options_buttons_script(self):
319+
"""
320+
Removes all 'More Options' buttons from the service catalog page.
321+
"""
322+
script = """
323+
function removeMoreOptionsButtons() {
324+
waLog('Searching for More Options buttons...', 'removeMoreOptionsButtons');
325+
let buttons = document.querySelectorAll('button.btn.btn-icon.icon-ellipsis');
326+
buttons.forEach((button) => {
327+
button.remove();
328+
});
329+
waLog('Removed ' + buttons.length + ' More Options buttons', 'removeMoreOptionsButtons');
330+
}
331+
332+
runInGsftMainOnlyAndProtectByURL(removeMoreOptionsButtons, 'com.glideapp.servicecatalog');
333+
"""
334+
return script
335+
279336
def setup_goal(self, page: Page) -> tuple[str, dict]:
280337
super().setup_goal(page=page)
281338

0 commit comments

Comments
 (0)