security: escape dynamic content in innerHTML to prevent XSS

This commit addresses several XSS vulnerabilities by ensuring that
dynamic data from external APIs (GitHub) and error messages are
properly escaped before being rendered via innerHTML.

Affected areas:
- openVersionMenu error handling and version list
- showErrorModal title and button text
- showToast message content

All changes were verified with a reproduction test case.

Co-authored-by: TauNeutrino <1600410+TauNeutrino@users.noreply.github.com>
This commit is contained in:
google-labs-jules[bot]
2026-03-10 12:37:54 +00:00
parent adc018d4d3
commit a2b2ec227f
3 changed files with 144 additions and 7 deletions

View File

@@ -899,7 +899,7 @@ export async function loadMenuDataFromAPI() {
import('./ui_helpers.js').then(uiHelpers => {
uiHelpers.showErrorModal(
'Keine Verbindung',
`Die Menüdaten konnten nicht geladen werden. Möglicherweise besteht keine Verbindung zur API oder zur Bessa-Webseite.<br><br><small style="color:var(--text-secondary)">${error.message}</small>`,
`Die Menüdaten konnten nicht geladen werden. Möglicherweise besteht keine Verbindung zur API oder zur Bessa-Webseite.<br><br><small style="color:var(--text-secondary)">${escapeHtml(error.message)}</small>`,
'Zur Original-Seite',
'https://web.bessa.app/knapp-kantine'
);
@@ -947,7 +947,7 @@ export function showToast(message, type = 'info') {
const toast = document.createElement('div');
toast.className = `toast toast-${type}`;
const icon = type === 'success' ? 'check_circle' : type === 'error' ? 'error' : 'info';
toast.innerHTML = `<span class="material-icons-round">${icon}</span><span>${message}</span>`;
toast.innerHTML = `<span class="material-icons-round">${icon}</span><span>${escapeHtml(message)}</span>`;
container.appendChild(toast);
requestAnimationFrame(() => toast.classList.add('show'));
setTimeout(() => {

View File

@@ -516,12 +516,12 @@ export function openVersionMenu() {
let action = '';
if (!isCurrent) {
action = `<a href="${v.url}" target="_blank" class="install-link" title="${v.tag} installieren">Installieren</a>`;
action = `<a href="${escapeHtml(v.url)}" target="_blank" class="install-link" title="${escapeHtml(v.tag)} installieren">Installieren</a>`;
}
li.innerHTML = `
<div class="version-info">
<strong>${v.tag}</strong>
<strong>${escapeHtml(v.tag)}</strong>
${badge}
</div>
${action}
@@ -554,7 +554,7 @@ export function openVersionMenu() {
}
} catch (e) {
container.innerHTML = `<p style="color:#e94560;">Fehler: ${e.message}</p>`;
container.innerHTML = `<p style="color:#e94560;">Fehler: ${escapeHtml(e.message)}</p>`;
}
}
@@ -661,7 +661,7 @@ export function showErrorModal(title, htmlContent, btnText, url) {
<div class="modal-header">
<h2 style="color: var(--error-color); display: flex; align-items: center; gap: 10px;">
<span class="material-icons-round">signal_wifi_off</span>
${title}
${escapeHtml(title)}
</h2>
</div>
<div style="padding: 20px;">
@@ -682,7 +682,7 @@ export function showErrorModal(title, htmlContent, btnText, url) {
justify-content: center;
transition: transform 0.1s;
">
${btnText}
${escapeHtml(btnText)}
<span class="material-icons-round">open_in_new</span>
</button>
</div>