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:
@@ -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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user