fix: resolve 14 memory leaks and bugs across native host and extension
CI / Build & Test (push) Successful in 2m47s

Critical:
- ssh-manager: release ports/wsServer/holesail on ptySpawn failure
- rdp-manager: add 30s timeout to holesailInst.ready() to prevent infinite hang

High:
- ssh-manager: cancel password-watch timers on PTY exit/error
- ssh-manager: cap outputSoFar to 4096 chars to prevent unbounded growth
- connect-proxy: add 64KB header buffer cap to prevent OOM

Medium:
- startup: assign tunnelsRestoredPromise before resolve() to fix race
- https-proxy: register upstream error handler before connect callback
- connect-proxy: register upstreamSocket error handler before connect callback
- https-proxy: destroy socket on backend stream error (was sending truncated 200)
- logs: debounce broadcastLogs to prevent IPC storm on every log call

Low:
- rdp-manager: cap VNC outputBuffer entry count (not just bytes)
- https-proxy: track connections in FakeHttpServer.connections Set
  instead of private _connections API
- tab-lifecycle: clean up subscribedTabs/dashboardTabs on tab navigation
- dashboard/events: guard setupEvents() against duplicate listener registration
This commit is contained in:
Raven Scott
2026-02-28 23:28:10 -05:00
parent 15caac7032
commit 31f30b2974
8 changed files with 88 additions and 31 deletions
+20 -11
View File
@@ -315,7 +315,9 @@ h1{color:#c0392b}code{background:#f4f4f4;padding:2px 6px;border-radius:3px;font-
}
proxyRes.on('data', (chunk) => !timedOut && res.write(chunk));
proxyRes.on('end', () => !timedOut && !res.writableEnded && res.end());
proxyRes.on('error', () => !res.writableEnded && res.end());
// Destroy the client socket on a mid-stream backend error so the browser
// receives a connection reset rather than a silently truncated 200 body.
proxyRes.on('error', () => { try { if (res.socket) res.socket.destroy(); } catch (_) {} });
});
proxyReq.on('error', (err) => {
clearTimeout(timeoutId);
@@ -355,7 +357,12 @@ function onUpgrade (req, socket, head) {
try { bareTcp = require('bare-tcp'); } catch (_) {}
if (!bareTcp) { socket.write('HTTP/1.1 502 Bad Gateway\r\n\r\n'); socket.destroy(); return; }
const upstream = bareTcp.connect(targetPort, targetHost, () => {
// Register error handler synchronously before the connect callback can fire,
// so an immediate ECONNREFUSED is never an unhandled error event.
const upstream = bareTcp.connect(targetPort, targetHost);
upstream.on('error', () => { try { socket.write('HTTP/1.1 502 Bad Gateway\r\n\r\n'); socket.destroy(); } catch (_) {} });
socket.on('error', () => { try { upstream.destroy(); } catch (_) {} });
upstream.on('connect', () => {
const headers = Object.entries(req.headers).map(([k, v]) => k + ': ' + v).join('\r\n');
const requestLine = (req.method || 'GET') + ' ' + (req.url || '/') + ' HTTP/1.1\r\n';
upstream.write(requestLine + headers + '\r\n\r\n');
@@ -363,8 +370,6 @@ function onUpgrade (req, socket, head) {
socket.pipe(upstream);
upstream.pipe(socket);
});
upstream.on('error', () => { try { socket.write('HTTP/1.1 502 Bad Gateway\r\n\r\n'); socket.destroy(); } catch (_) {} });
socket.on('error', () => { try { upstream.destroy(); } catch (_) {} });
}
// ---------------------------------------------------------------------------
@@ -515,7 +520,11 @@ function start (port, certsDirOrCA, callback, _baseDomains) {
// Attach the TCP server to the fake server so stop() can close it
fakeServer._tcpServer = tcpServer;
tcpServer.on('connection', handleRawConnection);
tcpServer.on('connection', (sock) => {
fakeServer.connections.add(sock);
sock.once('close', () => fakeServer.connections.delete(sock));
handleRawConnection(sock);
});
tcpServer.on('error', (err) => {
if (err.code === 'EADDRINUSE') {
@@ -552,13 +561,13 @@ function stop (callback) {
return;
}
// Destroy all raw TCP connections so close() fires immediately
const rawConns = tcpServer._connections;
if (rawConns && typeof rawConns[Symbol.iterator] === 'function') {
for (const socket of rawConns) {
try { socket.destroy(); } catch (_) {}
}
// Destroy all tracked raw TCP connections so close() fires immediately.
// We use fakeServer.connections (populated in the 'connection' handler above)
// instead of the undocumented tcpServer._connections internal.
for (const socket of fakeServer.connections) {
try { socket.destroy(); } catch (_) {}
}
fakeServer.connections.clear();
tcpServer.close(() => {
if (callback) callback();