fix: comprehensive bug fixes, security improvements, and feature additions
CI / Build & Test (push) Successful in 3m21s

Critical fixes:
- Fix wrong registry key (com.bridgeswarm → com.holesail.browser) in
  update-native-manifest-extension-id.ps1 — script was always failing on Windows
- Create missing wrong-domain.html redirect page for .host.test URLs
- Remove options_ui pointing to non-existent options.html from manifest

High-priority bug fixes:
- ssh-manager: track and kill orphaned printf FIFO writer when key auth succeeds
- ssh-manager: fix uncancelled 2000ms fallback password timer (assign to fallbackTimer,
  clear in cancelPasswordWatch); fix null-check before removeAllListeners
- ssh-manager: add 30s Promise.race timeout to holesailInst.ready()
- backup-manager: fix macOS cp -R nesting bug by removing destination before copy;
  add tar -tzf integrity check after archive creation
- host.js: restoreBackup now stops running tunnels before restore and re-starts them
- holesail-manager: fix stale closure bug in virtual host and service tunnel
  error/close handlers (guard with v.holesail === hs check)
- dashboard.js: remove dead setText('dashTabs', ...) call referencing non-existent element

Medium improvements:
- manifest: remove unused storage and scripting permissions; restrict
  web_accessible_resources match from <all_urls> to chrome-extension://*/*
- background.js: fix self-referential browser alias (globalThis.browser ?? chrome);
  add 30s per-request timeout to send(); clean up dashboardTabs on tab close
- holesail-manager: gate saveStateSync stderr log behind DEBUG flag; updateSettings
  now returns requiresRestart:true when proxy port changes; add backupRetention field
- host.js: pass requiresRestart through in updateSettings response
- dashboard.js: remove dead loadSettings() function; add requiresRestart warning toast;
  add chrome.runtime.lastError guards in fetchState and refreshBackups;
  set dynamic version from chrome.runtime.getManifest()
- dashboard.html: remove stray </button> tag; add id="sidebarVersion" for dynamic version
- install.sh/install.ps1: fetch version from RELEASE_BASE/VERSION instead of hardcoded 1.0.0
- install.ps1: add Firefox .xpi download and Firefox registry key
- update-native-manifest-extension-id.sh: add optional Firefox manifest update
- certificate-authority.js: defer RSA key generation to setImmediate to avoid blocking
  startup; expose caReady promise
- host.js: await caReady before starting HTTPS proxy

Documentation:
- REMOTE-DESKTOP.md: correct RDP WebSocket protocol field names to match rdp-manager.js
  (destLeft/destTop/destRight/destBottom, mouseMove/mouseButton/keyEvent/keyUnicode)

Feature additions:
- dashboard.js: add Reconnect button for service tunnels in error/closed state
- https-proxy.js: add WebSocket upgrade handler to support ws:// over *.hole.sail
- connect-proxy.js: add 10s header-read timeout to protect against idle connections
- native-host: add bare-fs as explicit dependency
This commit is contained in:
Raven Scott
2026-02-28 19:00:13 -05:00
parent a03f45a439
commit e5a1fa71fe
19 changed files with 436 additions and 110 deletions
+63
View File
@@ -77,6 +77,8 @@ function start(port, certsDirOrCA, callback) {
}
try {
proxyServer = https.createServer(opts, onRequest);
// Forward WebSocket upgrade requests to the backend tunnel
proxyServer.on('upgrade', onUpgrade);
} catch (err) {
if (process.stderr) process.stderr.write('[https-proxy] createServer threw: ' + err.message + '\n');
done(err);
@@ -236,6 +238,67 @@ h1{color:#c0392b}code{background:#f4f4f4;padding:2px 6px;border-radius:3px;font-
req.on('end', () => proxyReq.end());
}
/**
* Handle WebSocket upgrade requests by piping the raw socket to the backend.
* This allows ws:// / wss:// connections through *.hole.sail virtual hosts.
*/
function onUpgrade(req, socket, head) {
const hostHeader = req.headers && (req.headers.host || req.headers.Host);
const hostname = hostHeader ? hostHeader.split(':')[0].trim() : '';
debugLog('upgrade: hostname=', hostname, 'url=', req.url);
if (!getBackendForHostname || !hostname) {
socket.destroy();
return;
}
const backend = getBackendForHostname(hostname);
let targetHost = '127.0.0.1';
let targetPort = null;
if (backend != null && typeof backend === 'object' && typeof backend.port === 'number') {
targetHost = backend.host ?? '127.0.0.1';
targetPort = backend.port;
} else if (typeof backend === 'number') {
targetPort = backend;
}
if (targetPort == null) {
debugLog('upgrade: no backend for hostname=', hostname);
socket.write('HTTP/1.1 502 Bad Gateway\r\n\r\n');
socket.destroy();
return;
}
// Open a raw TCP connection to the backend and pipe the socket
let net = null;
try { net = require('bare-tcp'); } catch (_) {}
if (!net) {
debugLog('upgrade: bare-tcp not available');
socket.write('HTTP/1.1 502 Bad Gateway\r\n\r\n');
socket.destroy();
return;
}
const upstream = net.connect(targetPort, targetHost, () => {
// Reconstruct the HTTP upgrade request and forward it
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');
if (head && head.length > 0) upstream.write(head);
socket.pipe(upstream);
upstream.pipe(socket);
});
upstream.on('error', (err) => {
debugLog('upgrade: upstream error hostname=', hostname, 'err=', err.message);
try { socket.write('HTTP/1.1 502 Bad Gateway\r\n\r\n'); socket.destroy(); } catch (_) {}
});
socket.on('error', () => {
try { upstream.destroy(); } catch (_) {}
});
}
function stop(callback) {
if (!proxyServer) {
if (callback) callback();