From 9ce42f3965c0002b5359c0a34baafe5d369fa7bc Mon Sep 17 00:00:00 2001 From: Tobias Gesellchen Date: Sun, 10 May 2026 13:06:08 +0200 Subject: [PATCH] fix(ui): switch display-into-innerHTML status writes to textContent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sweeps the remaining instances of the same pattern that triggered CodeQL alert 132 in PR #240's review: status messages built by string-concatenating the user-controlled `display` (device name) into `.innerHTML`. None of these had ever needed HTML formatting; they're all plain status text. Converts 26 sites across reboot(), revert(), migrate(), showSummary(), trustCA(), ensureRemoteServices(), removeRemoteServices(), backup(), plus fetchDevices' error fallback and the loadAccount sync log line. The one site that genuinely needs intentional formatting — the migrate() success message ("Please reboot the device to activate the changes.") — is rebuilt with replaceChildren + createElement so the device name still flows through createTextNode rather than HTML parsing. Out of scope (intentionally left for a separate pass): the dashboard table rows, account-metadata templates, and the error.message-into- colored-span / redirectUrl-into-href patterns. Those are different classes and benefit from a focused refactor. Co-Authored-By: Claude Opus 4.7 (1M context) --- pkg/service/handlers/web/js/script.js | 69 +++++++++++++++------------ 1 file changed, 38 insertions(+), 31 deletions(-) diff --git a/pkg/service/handlers/web/js/script.js b/pkg/service/handlers/web/js/script.js index 05fc7ca..9ce47b1 100644 --- a/pkg/service/handlers/web/js/script.js +++ b/pkg/service/handlers/web/js/script.js @@ -364,7 +364,7 @@ async function fetchDevices() { fetchSpotifyStatus(); } } catch (error) { - document.getElementById("device-list").innerHTML = "Error loading devices: " + error; + document.getElementById("device-list").textContent = "Error loading devices: " + error; } } @@ -484,7 +484,7 @@ async function startSync() { status.style.backgroundColor = "#dfd"; status.textContent = "✅ Sync completed successfully for " + display + "!"; results.style.display = "block"; - log.innerHTML = "Data fetched and saved to local datastore for " + display + ".\nPresets: OK\nRecents: OK\nSources: OK"; + log.textContent = "Data fetched and saved to local datastore for " + display + ".\nPresets: OK\nRecents: OK\nSources: OK"; } else { const err = await response.text(); throw new Error(err); @@ -1709,7 +1709,7 @@ async function showSummary(deviceId) { statusDiv.style.backgroundColor = "#ffffcc"; const display = getDeviceDisplayName(deviceId); - statusDiv.innerHTML = "Fetching summary for " + display + "..."; + statusDiv.textContent = "Fetching summary for " + display + "..."; const outputBox = document.getElementById("command-output-box"); if (outputBox) outputBox.style.display = "none"; @@ -1872,7 +1872,7 @@ async function showSummary(deviceId) { document.getElementById("migration-summary").scrollIntoView(); } catch (error) { statusDiv.style.backgroundColor = "#ffcccc"; - statusDiv.innerHTML = "Error fetching summary for " + display + ": " + error; + statusDiv.textContent = "Error fetching summary for " + display + ": " + error; } } @@ -1910,7 +1910,7 @@ async function revert(deviceId, ip) { const statusDiv = document.getElementById("status"); statusDiv.style.display = "block"; statusDiv.style.backgroundColor = "#ffffcc"; - statusDiv.innerHTML = "Reverting " + display + " to defaults..."; + statusDiv.textContent = "Reverting " + display + " to defaults..."; try { const response = await fetch("/setup/revert/" + encodeURIComponent(deviceId), {method: "POST"},); @@ -1918,14 +1918,14 @@ async function revert(deviceId, ip) { showCommandOutput(result); if (result.ok) { statusDiv.style.backgroundColor = "#ccffcc"; - statusDiv.innerHTML = "Successfully started revert for " + display + "."; + statusDiv.textContent = "Successfully started revert for " + display + "."; } else { statusDiv.style.backgroundColor = "#ffcccc"; - statusDiv.innerHTML = "Revert failed for " + display + ": " + (result.message || "Unknown error"); + statusDiv.textContent = "Revert failed for " + display + ": " + (result.message || "Unknown error"); } } catch (error) { statusDiv.style.backgroundColor = "#ffcccc"; - statusDiv.innerHTML = "Error reverting " + display + ": " + error; + statusDiv.textContent = "Error reverting " + display + ": " + error; } } @@ -1958,14 +1958,14 @@ async function reboot(deviceId, ip) { showCommandOutput(result); if (result.ok) { statusDiv.style.backgroundColor = "#ccffcc"; - statusDiv.innerHTML = "Successfully started reboot for " + display + "."; + statusDiv.textContent = "Successfully started reboot for " + display + "."; } else { statusDiv.style.backgroundColor = "#ffcccc"; - statusDiv.innerHTML = "Reboot failed for " + display + ": " + (result.message || "Unknown error"); + statusDiv.textContent = "Reboot failed for " + display + ": " + (result.message || "Unknown error"); } } catch (error) { statusDiv.style.backgroundColor = "#ffcccc"; - statusDiv.innerHTML = "Error rebooting " + display + ": " + error; + statusDiv.textContent = "Error rebooting " + display + ": " + error; } } @@ -2110,7 +2110,7 @@ async function migrate(deviceId, ip) { statusDiv.style.display = "block"; statusDiv.style.backgroundColor = "#ffffcc"; const display = getDeviceDisplayName(deviceId); - statusDiv.innerHTML = "Migrating " + display + " using " + method + "..."; + statusDiv.textContent = "Migrating " + display + " using " + method + "..."; let query = "?method=" + encodeURIComponent(method) + "&target_url=" + encodeURIComponent(targetUrl); for (let k in opts) { @@ -2123,7 +2123,14 @@ async function migrate(deviceId, ip) { showCommandOutput(result); if (result.ok) { statusDiv.style.backgroundColor = "#ccffcc"; - statusDiv.innerHTML = "Successfully started migration for " + display + ". Please reboot the device to activate the changes."; + // replaceChildren keeps the user-controlled `display` outside any + // HTML-parsing context, while preserving the intentional . + statusDiv.replaceChildren( + document.createTextNode("Successfully started migration for " + display + ". "), + Object.assign(document.createElement("strong"), { + textContent: "Please reboot the device to activate the changes.", + }), + ); // Make reboot button available and prominent const rebootBtn = document.getElementById("reboot-speaker-btn"); @@ -2142,11 +2149,11 @@ async function migrate(deviceId, ip) { summaryDiv.style.display = "block"; } else { statusDiv.style.backgroundColor = "#ffcccc"; - statusDiv.innerHTML = "Migration failed for " + display + ": " + (result.message || "Unknown error"); + statusDiv.textContent = "Migration failed for " + display + ": " + (result.message || "Unknown error"); } } catch (error) { statusDiv.style.backgroundColor = "#ffcccc"; - statusDiv.innerHTML = "Error migrating " + display + ": " + error; + statusDiv.textContent = "Error migrating " + display + ": " + error; } } @@ -2159,7 +2166,7 @@ async function trustCA(deviceId, ip) { statusDiv.style.display = "block"; statusDiv.style.backgroundColor = "#ffffcc"; const display = getDeviceDisplayName(deviceId); - statusDiv.innerHTML = "Injecting Root CA into shared trust store on " + display + "..."; + statusDiv.textContent = "Injecting Root CA into shared trust store on " + display + "..."; try { const response = await fetch("/setup/trust-ca/" + encodeURIComponent(deviceId), {method: "POST"},); @@ -2167,15 +2174,15 @@ async function trustCA(deviceId, ip) { showCommandOutput(result); if (result.ok) { statusDiv.style.backgroundColor = "#ccffcc"; - statusDiv.innerHTML = "Successfully injected Root CA on " + display + "."; + statusDiv.textContent = "Successfully injected Root CA on " + display + "."; showSummary(deviceId); // Refresh to update status } else { statusDiv.style.backgroundColor = "#ffcccc"; - statusDiv.innerHTML = "Failed to trust CA on " + display + ": " + (result.message || "Unknown error"); + statusDiv.textContent = "Failed to trust CA on " + display + ": " + (result.message || "Unknown error"); } } catch (error) { statusDiv.style.backgroundColor = "#ffcccc"; - statusDiv.innerHTML = "Error trusting CA on " + display + ": " + error; + statusDiv.textContent = "Error trusting CA on " + display + ": " + error; } } @@ -2191,7 +2198,7 @@ async function ensureRemoteServices(deviceId, ip) { statusDiv.style.display = "block"; statusDiv.style.backgroundColor = "#ffffcc"; const display = getDeviceDisplayName(deviceId); - statusDiv.innerHTML = "Ensuring remote services for " + display + "..."; + statusDiv.textContent = "Ensuring remote services for " + display + "..."; try { const response = await fetch("/setup/ensure-remote-services/" + encodeURIComponent(deviceId), {method: "POST"},); @@ -2199,14 +2206,14 @@ async function ensureRemoteServices(deviceId, ip) { showCommandOutput(result); if (result.ok) { statusDiv.style.backgroundColor = "#ccffcc"; - statusDiv.innerHTML = "Successfully ensured remote services for " + display + "."; + statusDiv.textContent = "Successfully ensured remote services for " + display + "."; } else { statusDiv.style.backgroundColor = "#ffcccc"; - statusDiv.innerHTML = "Failed to ensure remote services for " + display + ": " + (result.message || "Unknown error"); + statusDiv.textContent = "Failed to ensure remote services for " + display + ": " + (result.message || "Unknown error"); } } catch (error) { statusDiv.style.backgroundColor = "#ffcccc"; - statusDiv.innerHTML = "Error ensuring remote services for " + display + ": " + error; + statusDiv.textContent = "Error ensuring remote services for " + display + ": " + error; } } @@ -2225,7 +2232,7 @@ async function removeRemoteServices(deviceId, ip) { const statusDiv = document.getElementById("status"); statusDiv.style.display = "block"; statusDiv.style.backgroundColor = "#ffffcc"; - statusDiv.innerHTML = "Removing remote services for " + display + "..."; + statusDiv.textContent = "Removing remote services for " + display + "..."; try { const response = await fetch("/setup/remove-remote-services/" + encodeURIComponent(deviceId), {method: "POST"},); @@ -2233,14 +2240,14 @@ async function removeRemoteServices(deviceId, ip) { showCommandOutput(result); if (result.ok) { statusDiv.style.backgroundColor = "#ccffcc"; - statusDiv.innerHTML = "Successfully removed remote services from " + display + "."; + statusDiv.textContent = "Successfully removed remote services from " + display + "."; } else { statusDiv.style.backgroundColor = "#ffcccc"; - statusDiv.innerHTML = "Failed to remove remote services for " + display + ": " + (result.message || "Unknown error"); + statusDiv.textContent = "Failed to remove remote services for " + display + ": " + (result.message || "Unknown error"); } } catch (error) { statusDiv.style.backgroundColor = "#ffcccc"; - statusDiv.innerHTML = "Error removing remote services for " + display + ": " + error; + statusDiv.textContent = "Error removing remote services for " + display + ": " + error; } } @@ -2253,7 +2260,7 @@ async function backupConfig(deviceId, ip) { statusDiv.style.display = "block"; statusDiv.style.backgroundColor = "#ffffcc"; const display = getDeviceDisplayName(deviceId); - statusDiv.innerHTML = "Creating backup for " + display + "..."; + statusDiv.textContent = "Creating backup for " + display + "..."; try { const response = await fetch("/setup/backup/" + encodeURIComponent(deviceId), {method: "POST"},); @@ -2261,15 +2268,15 @@ async function backupConfig(deviceId, ip) { showCommandOutput(result); if (result.ok) { statusDiv.style.backgroundColor = "#ccffcc"; - statusDiv.innerHTML = "Successfully created backup for " + display + "."; + statusDiv.textContent = "Successfully created backup for " + display + "."; showSummary(deviceId); // Refresh } else { statusDiv.style.backgroundColor = "#ffcccc"; - statusDiv.innerHTML = "Backup failed for " + display + ": " + (result.message || "Unknown error"); + statusDiv.textContent = "Backup failed for " + display + ": " + (result.message || "Unknown error"); } } catch (error) { statusDiv.style.backgroundColor = "#ffcccc"; - statusDiv.innerHTML = "Error creating backup for " + display + ": " + error; + statusDiv.textContent = "Error creating backup for " + display + ": " + error; } }