Merge pull request #42 from ruvnet/security/fix-critical-vulnerabilities

Security: Fix critical vulnerabilities (includes fr4iser90 PR #38 + fix)
This commit was merged in pull request #42.
This commit is contained in:
rUv
2026-02-28 21:44:00 -05:00
committed by GitHub
10 changed files with 226 additions and 66 deletions

View File

@@ -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 = `
<span class="feature-name">${this.formatFeatureName(feature)}</span>
<span class="feature-status">${enabled ? '✓' : '✗'}</span>
`;
// 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 = `
<span class="zone-name">${zoneId}</span>
<span class="zone-count">undefined</span>
`;
// 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 = `
<span class="zone-name">${zoneId}</span>
<span class="zone-count">${count}</span>
`;
// 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);
});
}

View File

@@ -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 = `
<div class="array-info">
<span class="info-label">Active TX:</span>
<span class="info-value">${txActive}/3</span>
</div>
<div class="array-info">
<span class="info-label">Active RX:</span>
<span class="info-value">${rxActive}/6</span>
</div>
<div class="array-info">
<span class="info-label">Signal Quality:</span>
<span class="info-value">${this.calculateSignalQuality(txActive, rxActive)}%</span>
</div>
`;
// 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

View File

@@ -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}<br>
Frames: ${this.state.frameCount}<br>
FPS: ${fps.toFixed(1)}<br>
Persons: ${persons}<br>
Zones: ${zones}<br>
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) {

View File

@@ -107,8 +107,12 @@ export function buildApiUrl(endpoint, params = {}) {
// Helper function to build WebSocket URLs
export function buildWsUrl(endpoint, params = {}) {
const protocol = window.location.protocol === 'https:'
? API_CONFIG.WSS_PREFIX
// Use secure WebSocket (wss://) when serving over HTTPS or on non-localhost
// Use ws:// only for localhost development
const isLocalhost = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1';
const isSecure = window.location.protocol === 'https:';
const protocol = (isSecure || !isLocalhost)
? API_CONFIG.WSS_PREFIX
: API_CONFIG.WS_PREFIX;
// Match Rust sensing server port