From 487ed069fccd1b746ac5b21b93d3123891c9e32d Mon Sep 17 00:00:00 2001 From: Louis Date: Fri, 25 Sep 2026 22:51:33 +0200 Subject: [PATCH] Fix main-process crash on SSH connection errors: handle repeated ssh2 error events, use the Windows OpenSSH agent only when it is running, show uncaught errors as a toast Co-Authored-By: Claude Opus 5.5 --- src/i18n.js | 1 + src/main/main.js | 7 +++++++ src/main/ssh.js | 22 +++++++++++++++++----- 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/i18n.js b/src/i18n.js index 02c4223..ce21f67 100644 --- a/src/i18n.js +++ b/src/i18n.js @@ -542,6 +542,7 @@ 'A static configuration needs at least one IPv4 address (e.g. 192.168.1.10/24).': 'Eine statische Konfiguration braucht mindestens eine IPv4-Adresse (z. B. 192.168.1.10/24).', 'A static IPv6 configuration needs at least one IPv6 address.': 'Eine statische IPv6-Konfiguration braucht mindestens eine IPv6-Adresse.', + 'Authentication failed for {user}. Check the username, password or key.': 'Anmeldung für {user} fehlgeschlagen. Prüfe Benutzername, Passwort oder Schlüssel.', // Main-Prozess 'Host key has changed!': 'Host-Schlüssel hat sich geändert!', 'Unknown host': 'Unbekannter Host', diff --git a/src/main/main.js b/src/main/main.js index c4575b0..feee36a 100644 --- a/src/main/main.js +++ b/src/main/main.js @@ -32,6 +32,13 @@ if (process.platform === 'linux' && readLaunch().x11 !== false && !process.argv. } const pendingSecrets = new Map(); +// Sicherheitsnetz: unerwartete Fehler (z. B. aus Netzwerkbibliotheken) als Meldung anzeigen statt den Hauptprozess abstürzen zu lassen +process.on('uncaughtException', (e) => { + console.error('Uncaught exception:', e); + send('toast', e?.message || String(e), 'error'); +}); +process.on('unhandledRejection', (e) => console.error('Unhandled rejection:', e)); + function send(channel, ...args) { if (win && !win.isDestroyed()) win.webContents.send(channel, ...args); } diff --git a/src/main/ssh.js b/src/main/ssh.js index 7345245..aa7b82a 100644 --- a/src/main/ssh.js +++ b/src/main/ssh.js @@ -9,7 +9,11 @@ const i18n = require('../i18n'); function defaultAgent() { if (process.env.SSH_AUTH_SOCK) return process.env.SSH_AUTH_SOCK; - if (process.platform === 'win32') return '\\\\.\\pipe\\openssh-ssh-agent'; + if (process.platform === 'win32') { + // Nur verwenden, wenn der Windows-OpenSSH-Agent läuft (sonst schlägt die Agent-Anmeldung unnötig fehl) + const pipe = '\\\\.\\pipe\\openssh-ssh-agent'; + try { return fs.existsSync(pipe) ? pipe : undefined; } catch { return undefined; } + } return undefined; } @@ -111,8 +115,16 @@ class SshManager { } finish(answers); }); - conn.once('ready', () => resolve(conn)); - conn.once('error', async (err) => { + // ssh2 kann mehrere 'error'-Ereignisse senden (z. B. Anmeldefehler und danach Handshake-Timeout). + // Ein dauerhafter Listener verhindert, dass ein späterer Fehler den Hauptprozess abstürzen lässt; + // Fehler nach dem Verbindungsaufbau melden die Sitzungen selbst (close/error). + let settled = false; + conn.on('error', () => {}); + conn.once('ready', () => { settled = true; resolve(conn); }); + conn.on('error', async (err) => { + if (settled) return; + settled = true; + try { conn.end(); } catch {} // Kein Passwort hinterlegt und alle Methoden schlugen fehl -> nach Passwort fragen und neu versuchen if (err.level === 'client-authentication' && !host.password && !host._retried) { const pw = await this.askSecret(sessionId, { title: i18n.t('Password'), prompt: i18n.t('Password for {user}', { user: `${cfg.username}@${host.address}` }), echo: false, host: host.label || host.address }); @@ -121,9 +133,9 @@ class SshManager { return; } } - reject(err); + reject(err.level === 'client-authentication' ? new Error(i18n.t('Authentication failed for {user}. Check the username, password or key.', { user: `${cfg.username}@${host.address}` })) : err); }); - try { conn.connect(cfg); } catch (e) { reject(e); } + try { conn.connect(cfg); } catch (e) { settled = true; reject(e); } }); }