mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-18 16:46:17 +00:00
fix(ui): close CodeQL js/xss-through-dom finding (PR #240 review)
CodeQL alert 132 flagged the reboot status line as a sink that received user-controlled DOM text (device names from the migration/sync select options and table rows) without escaping. Six data-flow paths converged on script.js:1950. Switch the sink at line 1950 from .innerHTML to .textContent — the status message has never needed HTML formatting. The pre-existing display-into-innerHTML pattern still exists elsewhere in this file but those lines aren't in this PR's scope and are tracked by their own historical alerts. Also harden the (newer) `currentP.innerHTML = ... <strong> + data.current + </strong> ...` line in loadAccountIDSuggestions: rebuild the paragraph with replaceChildren + createElement so the account ID never becomes HTML, even though it's expected to be a 7-digit string. Coerce known account IDs to String() when populating the existing-account dropdown so the IDE's type inference stops complaining about opt.value = id; / opt.textContent = id; on data of unknown[] type. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
0b579a7e59
commit
e3dac8b5a6
@@ -1947,7 +1947,8 @@ async function reboot(deviceId, ip) {
|
||||
const statusDiv = document.getElementById("status");
|
||||
statusDiv.style.display = "block";
|
||||
statusDiv.style.backgroundColor = "#ffffcc";
|
||||
statusDiv.innerHTML = "Rebooting " + display + " via " + rebootMethod + "...";
|
||||
// textContent avoids reinterpreting the (user-controlled) device name as HTML.
|
||||
statusDiv.textContent = "Rebooting " + display + " via " + rebootMethod + "...";
|
||||
|
||||
try {
|
||||
const url = "/setup/reboot/" + encodeURIComponent(deviceId)
|
||||
@@ -1993,19 +1994,22 @@ async function loadAccountIDSuggestions(deviceId) {
|
||||
|
||||
// Reset
|
||||
existingSelect.innerHTML = "<option value=\"\">-- pick from datastore --</option>";
|
||||
(data.known || []).forEach((id) => {
|
||||
(data.known || []).forEach((/** @type {string} */ id) => {
|
||||
const opt = document.createElement("option");
|
||||
opt.value = id;
|
||||
opt.textContent = id;
|
||||
opt.value = String(id);
|
||||
opt.textContent = String(id);
|
||||
existingSelect.appendChild(opt);
|
||||
});
|
||||
|
||||
if (data.current) {
|
||||
currentP.style.display = "block";
|
||||
currentP.innerHTML =
|
||||
"Speaker is already paired with account "
|
||||
+ "<strong>" + data.current + "</strong>"
|
||||
+ ". You can keep it (recommended) or re-pair to a different ID.";
|
||||
// Build the paragraph with createElement so the user-controlled
|
||||
// account ID never becomes HTML.
|
||||
currentP.replaceChildren(
|
||||
document.createTextNode("Speaker is already paired with account "),
|
||||
Object.assign(document.createElement("strong"), {textContent: data.current}),
|
||||
document.createTextNode(". You can keep it (recommended) or re-pair to a different ID."),
|
||||
);
|
||||
input.value = data.current;
|
||||
freshDiv.style.display = "block";
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user