diff --git a/electron/bridges/terminalBridge.cjs b/electron/bridges/terminalBridge.cjs index 89a2349b2..27af2eea2 100644 --- a/electron/bridges/terminalBridge.cjs +++ b/electron/bridges/terminalBridge.cjs @@ -18,7 +18,7 @@ const ptyProcessTree = require("./ptyProcessTree.cjs"); const sessionLogStreamManager = require("./sessionLogStreamManager.cjs"); const { detectShellKind } = require("./ai/ptyExec.cjs"); -const { trackSessionIdlePrompt } = require("./ai/shellUtils.cjs"); +const { stripAnsi, trackSessionIdlePrompt } = require("./ai/shellUtils.cjs"); const { createZmodemSentry } = require("./zmodemHelper.cjs"); const { discoverShells } = require("./shellDiscovery.cjs"); const moshHandshake = require("./moshHandshake.cjs"); @@ -904,6 +904,19 @@ function addBundledMoshRuntimeEnv(env, bareClient, opts = {}) { return env; } +function stripMoshPromptControls(text) { + // eslint-disable-next-line no-control-regex + return stripAnsi(text).replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g, ""); +} + +function isMoshPassphrasePrompt(tail) { + return /(^|[\r\n]).*passphrase.*:\s*$/i.test(stripMoshPromptControls(tail)); +} + +function isMoshPasswordPrompt(tail) { + return /(^|[\r\n]).*password:\s*$/i.test(stripMoshPromptControls(tail)); +} + function createMoshSshPasswordResponder(sshPty, password, passphrase) { if ( (typeof password !== "string" || password.length === 0) && @@ -922,14 +935,14 @@ function createMoshSshPasswordResponder(sshPty, password, passphrase) { if (!text) return; tail = (tail + text).slice(-512); - if (typeof passphrase === "string" && passphrase.length > 0 && !answeredPassphrase && /(^|[\r\n]).*passphrase.*:\s*$/i.test(tail)) { + if (typeof passphrase === "string" && passphrase.length > 0 && !answeredPassphrase && isMoshPassphrasePrompt(tail)) { answeredPassphrase = true; sshPty.write(`${passphrase}\r`); return; } if (typeof password !== "string" || password.length === 0 || answeredPassword) return; - if (!/(^|[\r\n]).*password:\s*$/i.test(tail)) return; + if (!isMoshPasswordPrompt(tail)) return; answeredPassword = true; sshPty.write(`${password}\r`); diff --git a/electron/bridges/terminalBridge.moshHandshakeSession.test.cjs b/electron/bridges/terminalBridge.moshHandshakeSession.test.cjs index 320132756..5dd3550e0 100644 --- a/electron/bridges/terminalBridge.moshHandshakeSession.test.cjs +++ b/electron/bridges/terminalBridge.moshHandshakeSession.test.cjs @@ -219,6 +219,19 @@ test("startMoshSession writes the saved password when ssh prompts for one", asyn assert.deepEqual(h.spawns[0].writes, ["saved-secret\r"]); }); +test("startMoshSession writes the saved password when ConPTY appends cursor controls to the prompt", async (t) => { + const h = makeHarness(t); + await h.bridge.startMoshSession( + h.event, + { ...h.options, password: "saved-secret" }, + { moshClientLookup: h.lookupOpts }, + ); + + h.spawns[0].emitData("alice@example.com's password: \x1b[?25h"); + + assert.deepEqual(h.spawns[0].writes, ["saved-secret\r"]); +}); + test("startMoshSession passes vault private keys to ssh via a temp identity file", async (t) => { const h = makeHarness(t); await h.bridge.startMoshSession(