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
This commit is contained in:
fr4iser
2026-02-28 20:40:00 +01:00
parent f9d125dfd8
commit 5db55fdd70
3 changed files with 76 additions and 34 deletions

View File

@@ -103,10 +103,18 @@ export class DashboardTab {
Object.entries(features).forEach(([feature, enabled]) => { Object.entries(features).forEach(([feature, enabled]) => {
const featureElement = document.createElement('div'); const featureElement = document.createElement('div');
featureElement.className = `feature-item ${enabled ? 'enabled' : 'disabled'}`; featureElement.className = `feature-item ${enabled ? 'enabled' : 'disabled'}`;
featureElement.innerHTML = `
<span class="feature-name">${this.formatFeatureName(feature)}</span> // Use textContent instead of innerHTML to prevent XSS
<span class="feature-status">${enabled ? '✓' : '✗'}</span> 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); featuresContainer.appendChild(featureElement);
}); });
} }
@@ -296,10 +304,18 @@ export class DashboardTab {
['zone_1', 'zone_2', 'zone_3', 'zone_4'].forEach(zoneId => { ['zone_1', 'zone_2', 'zone_3', 'zone_4'].forEach(zoneId => {
const zoneElement = document.createElement('div'); const zoneElement = document.createElement('div');
zoneElement.className = 'zone-item'; zoneElement.className = 'zone-item';
zoneElement.innerHTML = `
<span class="zone-name">${zoneId}</span> // Use textContent instead of innerHTML to prevent XSS
<span class="zone-count">undefined</span> 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); zonesContainer.appendChild(zoneElement);
}); });
return; return;
@@ -309,10 +325,18 @@ export class DashboardTab {
const zoneElement = document.createElement('div'); const zoneElement = document.createElement('div');
zoneElement.className = 'zone-item'; zoneElement.className = 'zone-item';
const count = typeof data === 'object' ? (data.person_count || data.count || 0) : data; const count = typeof data === 'object' ? (data.person_count || data.count || 0) : data;
zoneElement.innerHTML = `
<span class="zone-name">${zoneId}</span> // Use textContent instead of innerHTML to prevent XSS
<span class="zone-count">${count}</span> 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); zonesContainer.appendChild(zoneElement);
}); });
} }

View File

@@ -107,20 +107,29 @@ export class HardwareTab {
const txActive = activeAntennas.filter(a => a.classList.contains('tx')).length; const txActive = activeAntennas.filter(a => a.classList.contains('tx')).length;
const rxActive = activeAntennas.filter(a => a.classList.contains('rx')).length; const rxActive = activeAntennas.filter(a => a.classList.contains('rx')).length;
arrayStatus.innerHTML = ` // Clear and rebuild using safe DOM methods to prevent XSS
<div class="array-info"> arrayStatus.innerHTML = '';
<span class="info-label">Active TX:</span>
<span class="info-value">${txActive}/3</span> const createInfoDiv = (label, value) => {
</div> const div = document.createElement('div');
<div class="array-info"> div.className = 'array-info';
<span class="info-label">Active RX:</span>
<span class="info-value">${rxActive}/6</span> const labelSpan = document.createElement('span');
</div> labelSpan.className = 'info-label';
<div class="array-info"> labelSpan.textContent = label;
<span class="info-label">Signal Quality:</span>
<span class="info-value">${this.calculateSignalQuality(txActive, rxActive)}%</span> const valueSpan = document.createElement('span');
</div> 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 // Calculate signal quality based on active antennas

View File

@@ -539,14 +539,23 @@ export class PoseDetectionCanvas {
const persons = this.state.lastPoseData?.persons?.length || 0; const persons = this.state.lastPoseData?.persons?.length || 0;
const zones = Object.keys(this.state.lastPoseData?.zone_summary || {}).length; const zones = Object.keys(this.state.lastPoseData?.zone_summary || {}).length;
statsEl.innerHTML = ` // Use textContent instead of innerHTML to prevent XSS
Connection: ${this.state.connectionState}<br> statsEl.textContent = '';
Frames: ${this.state.frameCount}<br> const lines = [
FPS: ${fps.toFixed(1)}<br> `Connection: ${this.state.connectionState}`,
Persons: ${persons}<br> `Frames: ${this.state.frameCount}`,
Zones: ${zones}<br> `FPS: ${fps.toFixed(1)}`,
Uptime: ${uptime}s `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) { showError(message) {