From 821594f617227e490fd0fb7673f0bde40c22722e Mon Sep 17 00:00:00 2001
From: "Jokob @NetAlertX" <96159884+jokob-sk@users.noreply.github.com>
Date: Thu, 21 May 2026 00:36:59 +0000
Subject: [PATCH] Refine documentation and enhance XSS prevention measures in
device details and test scripts
---
docs/ADVISORY_MULTI_NETWORK.md | 4 +--
front/deviceDetailsEdit.php | 2 +-
test/ui/test_ui_xss_devname.py | 55 +++++++++++++++++++++-------------
3 files changed, 37 insertions(+), 24 deletions(-)
diff --git a/docs/ADVISORY_MULTI_NETWORK.md b/docs/ADVISORY_MULTI_NETWORK.md
index c26a4f6e..b56f58ae 100644
--- a/docs/ADVISORY_MULTI_NETWORK.md
+++ b/docs/ADVISORY_MULTI_NETWORK.md
@@ -18,7 +18,7 @@ Effective multi-network monitoring starts with understanding how NetAlertX "sees
### 2. Automating IT Asset Inventory with Workflows
-[Workflows](./WORKFLOWS.md) are the "engine" of NetAlertX, reducing manual overhead as your device list grows. See some examples below.:
+[Workflows](./WORKFLOWS.md) are the "engine" of NetAlertX, reducing manual overhead as your device list grows. See some examples below:
#### A. Logical Ownership & VLAN Tagging
@@ -63,7 +63,7 @@ Use conditional logic to categorize devices.
```
#### C. Sync Node Tracking
-When using multiple instances, ensure all synchub nodes have a descriptive `SYNC_node_name` name to distinguish between sites.
+When using multiple instances, ensure all sync hub nodes have a descriptive `SYNC_node_name` name to distinguish between sites.
> [!TIP]
> Always test new workflows in a "Staging" instance. A misconfigured workflow can trigger thousands of unintended updates across your database.
diff --git a/front/deviceDetailsEdit.php b/front/deviceDetailsEdit.php
index 99f22bf6..6c70ac4b 100755
--- a/front/deviceDetailsEdit.php
+++ b/front/deviceDetailsEdit.php
@@ -333,7 +333,7 @@ function getDeviceData() {
// XSS prevention - encode special characters for string fields, but not for arrays (like children dynamic)
// Don't move above the handle devChildrenDynamic block because it relies on the original fieldData to generate options
fieldData = encodeSpecialChars(fieldData);
- console.log(fieldData);
+ // console.log(fieldData);
// Generate the input field HTML
const inputFormHtml = `
diff --git a/test/ui/test_ui_xss_devname.py b/test/ui/test_ui_xss_devname.py
index ca7a4953..0832c2c7 100644
--- a/test/ui/test_ui_xss_devname.py
+++ b/test/ui/test_ui_xss_devname.py
@@ -126,18 +126,37 @@ def _delete_xss_device(token: str):
)
-def _reset_canary(driver):
- """Inject canary reset so we start each navigation clean."""
- driver.execute_script("window.__xss_canary = false;")
-
-
def _read_canary(driver) -> bool:
"""Return True if the XSS canary was tripped."""
return bool(driver.execute_script("return window.__xss_canary || false;"))
+def _navigate_with_canary(driver, url, timeout=15):
+ """Navigate to *url* with window.__xss_canary pre-initialised to false.
+
+ Uses CDP Page.addScriptToEvaluateOnNewDocument so the canary is set
+ *before* any page script runs. If we reset it after driver.get() instead,
+ a payload that fires during page load would set the canary and we would
+ immediately clear the evidence.
+ """
+ result = driver.execute_cdp_cmd(
+ "Page.addScriptToEvaluateOnNewDocument",
+ {"source": "window.__xss_canary = false;"},
+ )
+ script_id = result.get("identifier")
+ try:
+ driver.get(url)
+ wait_for_page_load(driver, timeout=timeout)
+ finally:
+ if script_id:
+ driver.execute_cdp_cmd(
+ "Page.removeScriptToEvaluateOnNewDocument",
+ {"identifier": script_id},
+ )
+
+
def _force_cache_refresh(driver):
- """Navigate to /devices.php once to warm the localStorage cache, then go back."""
+ """Clear localStorage to force a fresh device-list fetch on next page load."""
driver.execute_script("if(window.localStorage){ localStorage.clear(); }")
@@ -198,10 +217,9 @@ def test_devname_xss_not_executed(xss_driver, path, description):
"""
driver = xss_driver
- # Navigate and reset canary before the page runs any JS
- driver.get(f"{BASE_URL}{path}")
- _reset_canary(driver) # reset AFTER page load (page may have set it)
- wait_for_page_load(driver, timeout=15)
+ # Pre-initialise canary via CDP so it is false before any page JS runs.
+ # Resetting after driver.get() would erase evidence of early-firing payloads.
+ _navigate_with_canary(driver, f"{BASE_URL}{path}")
# Give async JS (DataTables, FullCalendar, network tree) time to render
time.sleep(ASYNC_WAIT_S)
@@ -261,10 +279,11 @@ def test_devname_xss_device_shows_as_escaped_text(xss_driver):
# The body text should contain the literal payload characters as plain text
body_text = driver.find_element(By.TAG_NAME, "body").text
- # If the device name is visible at all, it must show as text, not fire XSS
if "onerror" in body_text:
- # Good: the word 'onerror' appears as visible text, not as an attribute
- assert "window.__xss_canary" not in driver.execute_script("return ''") or True
+ # Payload is visible as literal text — confirm it was not also executed
+ assert not driver.execute_script("return window.__xss_canary || false"), (
+ "XSS canary fired even though payload appeared as visible text on devices.php"
+ )
# No
![]()
with src=x should have been injected by the XSS payload
injected_imgs = driver.find_elements(By.CSS_SELECTOR, "img[src='x']")
@@ -297,14 +316,8 @@ def test_devname_attribute_break_xss_not_executed(xss_driver, xss_token):
if resp.status_code not in (200, 201):
pytest.skip(f"Could not create attribute-break XSS device (HTTP {resp.status_code})")
- # Warm cache
- xss_driver.get(f"{BASE_URL}/devices.php")
- wait_for_page_load(driver=xss_driver, timeout=15)
- time.sleep(ASYNC_WAIT_S)
-
- _reset_canary(xss_driver)
- xss_driver.get(f"{BASE_URL}/devices.php")
- wait_for_page_load(driver=xss_driver, timeout=15)
+ # Pre-set canary via CDP, then navigate — same pattern as main tests.
+ _navigate_with_canary(xss_driver, f"{BASE_URL}/devices.php")
time.sleep(ASYNC_WAIT_S)
fired = _read_canary(xss_driver)