fix(dash): address safety constraints, validation, and test quality issues

This commit is contained in:
syntaxbullet
2026-01-08 21:08:47 +01:00
parent 1251df286e
commit 8253de9f73
4 changed files with 191 additions and 64 deletions

View File

@@ -51,6 +51,11 @@ export async function createWebServer(config: WebServerConfig = {}): Promise<Web
}
}
// Configuration constants
const MAX_CONNECTIONS = 10;
const MAX_PAYLOAD_BYTES = 16384; // 16KB
const IDLE_TIMEOUT_SECONDS = 60;
// Interval for broadcasting stats to all connected WS clients
let statsBroadcastInterval: Timer | undefined;
@@ -62,6 +67,13 @@ export async function createWebServer(config: WebServerConfig = {}): Promise<Web
// Upgrade to WebSocket
if (url.pathname === "/ws") {
// Security Check: limit concurrent connections
const currentConnections = server.pendingWebSockets;
if (currentConnections >= MAX_CONNECTIONS) {
console.warn(`⚠️ [WS] Connection rejected: limit reached (${currentConnections}/${MAX_CONNECTIONS})`);
return new Response("Connection limit reached", { status: 429 });
}
const success = server.upgrade(req);
if (success) return undefined;
return new Response("WebSocket upgrade failed", { status: 400 });
@@ -137,30 +149,43 @@ export async function createWebServer(config: WebServerConfig = {}): Promise<Web
}, 5000);
}
},
message(ws, message) {
// Handle messages if needed (e.g. heartbeat)
async message(ws, message) {
try {
const data = JSON.parse(message.toString());
if (data.type === "PING") ws.send(JSON.stringify({ type: "PONG" }));
} catch (e) { }
const { WsMessageSchema } = await import("@shared/modules/dashboard/dashboard.types");
const parsed = WsMessageSchema.safeParse(JSON.parse(message.toString()));
if (!parsed.success) {
console.error("❌ [WS] Invalid message format:", parsed.error.issues);
return;
}
if (parsed.data.type === "PING") {
ws.send(JSON.stringify({ type: "PONG" }));
}
} catch (e) {
console.error("❌ [WS] Failed to parse message:", e);
}
},
close(ws) {
ws.unsubscribe("dashboard");
console.log(`🔌 [WS] Client disconnected.`);
console.log(`🔌 [WS] Client disconnected. Total remaining: ${server.pendingWebSockets}`);
// Stop broadcast interval if no clients left
if (server.pendingWebSockets === 0 && statsBroadcastInterval) {
clearInterval(statsBroadcastInterval);
statsBroadcastInterval = undefined;
}
}
},
maxPayloadLength: MAX_PAYLOAD_BYTES,
idleTimeout: IDLE_TIMEOUT_SECONDS,
},
development: isDev,
});
/**
* Helper to fetch full dashboard stats object
* Helper to fetch full dashboard stats object.
* Unified for both HTTP API and WebSocket broadcasts.
*/
async function getFullDashboardStats() {
// Import services (dynamic to avoid circular deps)
@@ -189,7 +214,7 @@ export async function createWebServer(config: WebServerConfig = {}): Promise<Web
},
recentEvents: recentEvents.map(event => ({
...event,
timestamp: event.timestamp.toISOString(),
timestamp: event.timestamp instanceof Date ? event.timestamp.toISOString() : event.timestamp,
})),
uptime: clientStats.uptime,
lastCommandTimestamp: clientStats.lastCommandTimestamp,