From 5db55fdd70c76d5a77e470f1967805d31369e810 Mon Sep 17 00:00:00 2001 From: fr4iser Date: Sat, 28 Feb 2026 20:40:00 +0100 Subject: [PATCH] security: Fix XSS vulnerabilities in UI components - Replace innerHTML with textContent and createElement - Use safe DOM manipulation methods - Prevents XSS attacks through user-controlled data --- ui/components/DashboardTab.js | 48 +++++++++++++++++++++------- ui/components/HardwareTab.js | 37 +++++++++++++-------- ui/components/PoseDetectionCanvas.js | 25 ++++++++++----- 3 files changed, 76 insertions(+), 34 deletions(-) diff --git a/ui/components/DashboardTab.js b/ui/components/DashboardTab.js index 25984ed..2ce878e 100644 --- a/ui/components/DashboardTab.js +++ b/ui/components/DashboardTab.js @@ -103,10 +103,18 @@ export class DashboardTab { Object.entries(features).forEach(([feature, enabled]) => { const featureElement = document.createElement('div'); featureElement.className = `feature-item ${enabled ? 'enabled' : 'disabled'}`; - featureElement.innerHTML = ` - ${this.formatFeatureName(feature)} - ${enabled ? '✓' : '✗'} - `; + + // Use textContent instead of innerHTML to prevent XSS + const featureNameSpan = document.createElement('span'); + featureNameSpan.className = 'feature-name'; + featureNameSpan.textContent = this.formatFeatureName(feature); + + const featureStatusSpan = document.createElement('span'); + featureStatusSpan.className = 'feature-status'; + featureStatusSpan.textContent = enabled ? '✓' : '✗'; + + featureElement.appendChild(featureNameSpan); + featureElement.appendChild(featureStatusSpan); featuresContainer.appendChild(featureElement); }); } @@ -296,10 +304,18 @@ export class DashboardTab { ['zone_1', 'zone_2', 'zone_3', 'zone_4'].forEach(zoneId => { const zoneElement = document.createElement('div'); zoneElement.className = 'zone-item'; - zoneElement.innerHTML = ` - ${zoneId} - undefined - `; + + // Use textContent instead of innerHTML to prevent XSS + const zoneNameSpan = document.createElement('span'); + zoneNameSpan.className = 'zone-name'; + zoneNameSpan.textContent = zoneId; + + const zoneCountSpan = document.createElement('span'); + zoneCountSpan.className = 'zone-count'; + zoneCountSpan.textContent = 'undefined'; + + zoneElement.appendChild(zoneNameSpan); + zoneElement.appendChild(zoneCountSpan); zonesContainer.appendChild(zoneElement); }); return; @@ -309,10 +325,18 @@ export class DashboardTab { const zoneElement = document.createElement('div'); zoneElement.className = 'zone-item'; const count = typeof data === 'object' ? (data.person_count || data.count || 0) : data; - zoneElement.innerHTML = ` - ${zoneId} - ${count} - `; + + // Use textContent instead of innerHTML to prevent XSS + const zoneNameSpan = document.createElement('span'); + zoneNameSpan.className = 'zone-name'; + zoneNameSpan.textContent = zoneId; + + const zoneCountSpan = document.createElement('span'); + zoneCountSpan.className = 'zone-count'; + zoneCountSpan.textContent = String(count); + + zoneElement.appendChild(zoneNameSpan); + zoneElement.appendChild(zoneCountSpan); zonesContainer.appendChild(zoneElement); }); } diff --git a/ui/components/HardwareTab.js b/ui/components/HardwareTab.js index baef164..7f36113 100644 --- a/ui/components/HardwareTab.js +++ b/ui/components/HardwareTab.js @@ -107,20 +107,29 @@ export class HardwareTab { const txActive = activeAntennas.filter(a => a.classList.contains('tx')).length; const rxActive = activeAntennas.filter(a => a.classList.contains('rx')).length; - arrayStatus.innerHTML = ` -
- Active TX: - ${txActive}/3 -
-
- Active RX: - ${rxActive}/6 -
-
- Signal Quality: - ${this.calculateSignalQuality(txActive, rxActive)}% -
- `; + // Clear and rebuild using safe DOM methods to prevent XSS + arrayStatus.innerHTML = ''; + + const createInfoDiv = (label, value) => { + const div = document.createElement('div'); + div.className = 'array-info'; + + const labelSpan = document.createElement('span'); + labelSpan.className = 'info-label'; + labelSpan.textContent = label; + + const valueSpan = document.createElement('span'); + valueSpan.className = 'info-value'; + valueSpan.textContent = value; + + div.appendChild(labelSpan); + div.appendChild(valueSpan); + return div; + }; + + arrayStatus.appendChild(createInfoDiv('Active TX:', `${txActive}/3`)); + arrayStatus.appendChild(createInfoDiv('Active RX:', `${rxActive}/6`)); + arrayStatus.appendChild(createInfoDiv('Signal Quality:', `${this.calculateSignalQuality(txActive, rxActive)}%`)); } // Calculate signal quality based on active antennas diff --git a/ui/components/PoseDetectionCanvas.js b/ui/components/PoseDetectionCanvas.js index 20bc838..62f9814 100644 --- a/ui/components/PoseDetectionCanvas.js +++ b/ui/components/PoseDetectionCanvas.js @@ -539,14 +539,23 @@ export class PoseDetectionCanvas { const persons = this.state.lastPoseData?.persons?.length || 0; const zones = Object.keys(this.state.lastPoseData?.zone_summary || {}).length; - statsEl.innerHTML = ` - Connection: ${this.state.connectionState}
- Frames: ${this.state.frameCount}
- FPS: ${fps.toFixed(1)}
- Persons: ${persons}
- Zones: ${zones}
- Uptime: ${uptime}s - `; + // Use textContent instead of innerHTML to prevent XSS + statsEl.textContent = ''; + const lines = [ + `Connection: ${this.state.connectionState}`, + `Frames: ${this.state.frameCount}`, + `FPS: ${fps.toFixed(1)}`, + `Persons: ${persons}`, + `Zones: ${zones}`, + `Uptime: ${uptime}s` + ]; + lines.forEach((line, index) => { + if (index > 0) { + statsEl.appendChild(document.createElement('br')); + } + const textNode = document.createTextNode(line); + statsEl.appendChild(textNode); + }); } showError(message) {