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
+12 -6
View File
@@ -111,7 +111,6 @@ function saveStateSync() {
fs.mkdirSync(path.dirname(file), { recursive: true });
fs.writeFileSync(file, data, 'utf8');
debugLog('state saved path=', file, 'servers=', serversList.length, 'vhosts=', virtualHostsList.length);
if (process.stderr) process.stderr.write('[holesail-manager] state saved to ' + file + '\n');
} catch (e) {
if (process.stderr) process.stderr.write('[holesail-manager] state save failed: ' + e.message + ' (path: ' + file + ')\n');
}
@@ -240,19 +239,24 @@ function getSettings() {
}
function updateSettings(patch) {
if (!patch || typeof patch !== 'object') return;
if (!patch || typeof patch !== 'object') return { requiresRestart: false };
let requiresRestart = false;
if (typeof patch.proxyPort === 'number' && patch.proxyPort > 0 && patch.proxyPort < 65536) {
if (patch.proxyPort !== currentSettings.proxyPort) requiresRestart = true;
currentSettings.proxyPort = patch.proxyPort;
runtimeProxyPort = patch.proxyPort;
}
if (typeof patch.connectProxyPort === 'number' && patch.connectProxyPort > 0 && patch.connectProxyPort < 65536) {
if (patch.connectProxyPort !== currentSettings.connectProxyPort) requiresRestart = true;
currentSettings.connectProxyPort = patch.connectProxyPort;
}
if (typeof patch.readyTimeoutMs === 'number') currentSettings.readyTimeoutMs = patch.readyTimeoutMs;
if (typeof patch.notifyOnDisconnect === 'boolean') currentSettings.notifyOnDisconnect = patch.notifyOnDisconnect;
if (typeof patch.debug === 'boolean') currentSettings.debug = patch.debug;
if (typeof patch.disableOnFileUrls === 'boolean') currentSettings.disableOnFileUrls = patch.disableOnFileUrls;
if (typeof patch.backupRetention === 'number') currentSettings.backupRetention = patch.backupRetention;
saveStateSync();
return { requiresRestart };
}
// ── SSH Connections ───────────────────────────────────────────────────────────
@@ -406,12 +410,13 @@ async function setVirtualHost(payload) {
hs.on('error', (err) => {
if (process.stderr) process.stderr.write('[holesail-manager] tunnel error ' + hostname + ': ' + (err && err.message) + '\n');
const v = virtualHosts.get(hostname);
if (v) v.state = 'error';
// Guard against stale closures: only mutate the entry that owns this instance
if (v && v.holesail === hs) v.state = 'error';
emit('tunnelError', { hostname, error: err && err.message });
});
hs.on('close', () => {
const v = virtualHosts.get(hostname);
if (v) v.state = 'closed';
if (v && v.holesail === hs) v.state = 'closed';
emit('tunnelClosed', { hostname });
});
}
@@ -611,12 +616,13 @@ async function startServiceTunnel(payload) {
hs.on('error', (err) => {
if (process.stderr) process.stderr.write('[holesail-manager] service tunnel error ' + tunnelId + ': ' + (err && err.message) + '\n');
const t = serviceTunnels.get(tunnelId);
if (t) t.state = 'error';
// Guard against stale closures: only mutate the entry that owns this instance
if (t && t.holesail === hs) t.state = 'error';
emit('serviceTunnelError', { tunnelId, label, error: err && err.message });
});
hs.on('close', () => {
const t = serviceTunnels.get(tunnelId);
if (t) t.state = 'closed';
if (t && t.holesail === hs) t.state = 'closed';
emit('serviceTunnelClosed', { tunnelId, label });
});
}