From 9a0abc02d120c410dd246cfef033194245245dee Mon Sep 17 00:00:00 2001 From: Sami Date: Sat, 7 Mar 2026 02:06:55 -0800 Subject: [PATCH] Fix remaining frontend security issues (3 medium, 2 low) - Escape user-input port number in app-selector innerHTML - Replace inline onclick with addEventListener in backup history (HTML entity decode bypass) - Add Content-Security-Policy meta tag with script hash - Replace document.write with textContent for footer year - Filter __proto__/constructor/prototype in Object.assign calls Co-Authored-By: Claude Opus 4.6 --- status/index.html | 5 ++++- status/js/app-selector.js | 2 +- status/js/backup-restore.js | 6 +++++- status/js/globals.js | 13 ++++++++++--- 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/status/index.html b/status/index.html index 158ff27..d91c55d 100644 --- a/status/index.html +++ b/status/index.html @@ -8,6 +8,7 @@ + @@ -534,13 +535,15 @@ if (el) el.style.display = 'none'; } } + var yr = document.getElementById('footer-year'); + if (yr) yr.textContent = new Date().getFullYear(); })();
- © + ©
diff --git a/status/js/app-selector.js b/status/js/app-selector.js index e35835f..0733a5f 100644 --- a/status/js/app-selector.js +++ b/status/js/app-selector.js @@ -587,7 +587,7 @@ const result = await checkPortAvailability(portToCheck); if (result.available) { - portStatus.innerHTML = `Port ${portToCheck} is available`; + portStatus.innerHTML = `Port ${escapeHtml(String(portToCheck))} is available`; } else { const suggestedPort = await getSuggestedPort(defaultPort); portStatus.innerHTML = ` diff --git a/status/js/backup-restore.js b/status/js/backup-restore.js index 1f92274..c328de6 100644 --- a/status/js/backup-restore.js +++ b/status/js/backup-restore.js @@ -381,7 +381,7 @@ ${escapeHtml(bk.name || 'backup')}
${escapeHtml(bk.status)} - ${bk.status === 'success' ? `` : ''} + ${bk.status === 'success' ? `` : ''}
@@ -392,6 +392,10 @@ } html += '
'; historyContainer.innerHTML = html; + // Wire restore buttons with addEventListener (not inline onclick — HTML entity decode bypass) + historyContainer.querySelectorAll('.backup-restore-btn').forEach(btn => { + btn.addEventListener('click', () => window.__restoreServerBackup(btn.dataset.backupId)); + }); } catch (e) { historyContainer.innerHTML = `
Failed: ${escapeHtml(e.message)}
`; } diff --git a/status/js/globals.js b/status/js/globals.js index e2544f0..859178b 100644 --- a/status/js/globals.js +++ b/status/js/globals.js @@ -47,8 +47,10 @@ const SITE = { SITE.dnsIp = c.dns.ip || ''; SITE.dnsPort = c.dns.port || DC.DEFAULTS.DNS_PORT; } - if (c.dnsServers) { - Object.assign(SITE.dnsServers, c.dnsServers); + if (c.dnsServers && typeof c.dnsServers === 'object') { + for (const [k, v] of Object.entries(c.dnsServers)) { + if (k !== '__proto__' && k !== 'constructor' && k !== 'prototype') SITE.dnsServers[k] = v; + } } if (c.configurationType) SITE.configurationType = c.configurationType; if (c.domain) SITE.domain = c.domain; @@ -352,7 +354,12 @@ const AppState = { }, updateApp(id, changes) { const app = this._apps.find(a => a.id === id); - if (app) { Object.assign(app, changes); DC_BUS.emit('apps:changed', this._apps); } + if (app) { + for (const [k, v] of Object.entries(changes)) { + if (k !== '__proto__' && k !== 'constructor' && k !== 'prototype') app[k] = v; + } + DC_BUS.emit('apps:changed', this._apps); + } return app; } };