diff --git a/electron/bridges/terminalBridge.cjs b/electron/bridges/terminalBridge.cjs index 89a2349b..b50e18e1 100644 --- a/electron/bridges/terminalBridge.cjs +++ b/electron/bridges/terminalBridge.cjs @@ -25,6 +25,7 @@ const moshHandshake = require("./moshHandshake.cjs"); const tempDirBridge = require("./tempDirBridge.cjs"); const { createTelnetAutoLogin } = require("./telnetAutoLogin.cjs"); const telnetProtocol = require("./telnetProtocol.cjs"); +const { isAutoFillablePasswordChallenge } = require("./sshAuthHelper.cjs"); const execFileAsync = promisify(execFile); @@ -928,14 +929,27 @@ function createMoshSshPasswordResponder(sshPty, password, passphrase) { return; } - if (typeof password !== "string" || password.length === 0 || answeredPassword) return; - if (!/(^|[\r\n]).*password:\s*$/i.test(tail)) return; + if (answeredPassword || !isMoshAutoFillablePasswordPrompt(tail, password)) return; answeredPassword = true; sshPty.write(`${password}\r`); }; } +function getLatestMoshPromptLine(text) { + const lines = String(text || "").split(/[\r\n]+/); + for (let index = lines.length - 1; index >= 0; index -= 1) { + if (lines[index].trim().length > 0) return lines[index]; + } + return ""; +} + +function isMoshAutoFillablePasswordPrompt(text, password) { + const prompt = getLatestMoshPromptLine(text); + if (!/[::]\s*$/.test(prompt)) return false; + return isAutoFillablePasswordChallenge([{ prompt, echo: false }], password); +} + function normalizeMoshIdentityPath(keyPath) { if (typeof keyPath !== "string") return null; const trimmed = keyPath.trim(); diff --git a/electron/bridges/terminalBridge.moshHandshakeSession.test.cjs b/electron/bridges/terminalBridge.moshHandshakeSession.test.cjs index 32013275..85ce1b0b 100644 --- a/electron/bridges/terminalBridge.moshHandshakeSession.test.cjs +++ b/electron/bridges/terminalBridge.moshHandshakeSession.test.cjs @@ -219,6 +219,45 @@ 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 for localized password prompts", async (t) => { + const h = makeHarness(t); + await h.bridge.startMoshSession( + h.event, + { ...h.options, password: "saved-secret" }, + { moshClientLookup: h.lookupOpts }, + ); + + h.spawns[0].emitData("密码:"); + + assert.deepEqual(h.spawns[0].writes, ["saved-secret\r"]); +}); + +test("startMoshSession writes the saved password when the prompt chunk ends with newline", async (t) => { + const h = makeHarness(t); + await h.bridge.startMoshSession( + h.event, + { ...h.options, password: "saved-secret" }, + { moshClientLookup: h.lookupOpts }, + ); + + h.spawns[0].emitData("Password:\r\n"); + + assert.deepEqual(h.spawns[0].writes, ["saved-secret\r"]); +}); + +test("startMoshSession does not write saved password into OTP prompts", async (t) => { + const h = makeHarness(t); + await h.bridge.startMoshSession( + h.event, + { ...h.options, password: "saved-secret" }, + { moshClientLookup: h.lookupOpts }, + ); + + h.spawns[0].emitData("One-time password:"); + + assert.deepEqual(h.spawns[0].writes, []); +}); + test("startMoshSession passes vault private keys to ssh via a temp identity file", async (t) => { const h = makeHarness(t); await h.bridge.startMoshSession(