From 284e685ed34ef99ab2a1608e7ce1197b88b8eb90 Mon Sep 17 00:00:00 2001 From: Rene Luria Date: Wed, 8 Jul 2026 13:51:45 +0200 Subject: [PATCH] fix(security): remove innerHTML XSS sink in showAlert --- templates/index.html | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/templates/index.html b/templates/index.html index 102e0e4..ac15cfa 100644 --- a/templates/index.html +++ b/templates/index.html @@ -160,7 +160,26 @@ * @param {string} type - Bootstrap alert type (success, danger, warning, info) */ showAlert(element, message, type = 'info') { - element.innerHTML = ``; + element.replaceChildren(); + const alert = document.createElement('div'); + alert.className = `alert alert-${type}`; + alert.setAttribute('role', 'alert'); + if (typeof message === 'string') { + alert.textContent = message; + } else if (message && typeof message === 'object') { + if (message.title) { + const strong = document.createElement('strong'); + strong.textContent = message.title; + alert.appendChild(strong); + } + if (message.body) { + if (message.title) { + alert.appendChild(document.createTextNode(' ')); + } + alert.appendChild(document.createTextNode(message.body)); + } + } + element.appendChild(alert); }, /** @@ -465,7 +484,7 @@ ui.showUserInfo(user); } - utils.showAlert(elements.secretResult, `Secret Phrase: ${cachedSecret}`, 'success'); + utils.showAlert(elements.secretResult, { title: 'Secret Phrase:', body: cachedSecret }, 'success'); } else { // Fetch fresh secret phrase and user info setTimeout(ui.getSecretPhrase, 500); @@ -581,7 +600,7 @@ localStorage.setItem('secret_phrase', data.secret_phrase); localStorage.setItem('secret_last_fetch', Date.now().toString()); - utils.showAlert(elements.secretResult, `Secret Phrase: ${data.secret_phrase}`, 'success'); + utils.showAlert(elements.secretResult, { title: 'Secret Phrase:', body: data.secret_phrase }, 'success'); } else { // Clear cached secret if user is no longer authorized localStorage.removeItem('secret_phrase'); @@ -617,8 +636,8 @@ if (data && data.bitcoin && data.bitcoin.chf) { const price = data.bitcoin.chf; utils.showAlert( - elements.btcPriceResult, - `1 Bitcoin (BTC) = CHF ${price.toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2})}`, + elements.btcPriceResult, + { title: `1 Bitcoin (BTC) = CHF ${price.toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2})}` }, 'success' ); } else {