ssh hone
CI / Build & Test (push) Successful in 2m55s

This commit is contained in:
Raven Scott
2026-02-28 17:31:42 -05:00
parent 8ae54f7816
commit bd2cccaa38
+36 -18
View File
@@ -356,33 +356,51 @@ async function startSession(payload) {
});
}
// Called on every PTY data chunk during the auth window.
// Resets the quiet timer; if the PTY produces shell-like output
// (non-SSH-banner content with printable chars beyond the first second)
// we assume key auth succeeded and stop watching.
const spawnTime = Date.now();
// Watch PTY output to decide whether to show a password prompt.
//
// Key insight: when public key auth succeeds, SSH immediately starts
// streaming the MOTD/banner/shell prompt — substantial output arrives
// within the first few hundred ms. When key auth fails and SSH invokes
// the askpass helper, the PTY goes completely silent (the helper is
// blocked waiting for the FIFO).
//
// Rules:
// 1. Don't start the quiet timer until the PTY has produced at least
// one chunk of output (proves SSH connected and started auth).
// 2. Once output has arrived, accumulate it. If it contains typical
// post-login content (MOTD keywords, shell prompt chars) cancel the
// password watch — we are already logged in.
// 3. Only show the password prompt after 600 ms of silence AND we have
// NOT seen post-login content.
let seenOutput = false;
let outputSoFar = '';
// Patterns that indicate a successful login (MOTD / shell prompt)
const LOGIN_RE = /Welcome|Last login|Ubuntu|Debian|CentOS|Alpine|[$#>%]\s*$/i;
function onPtyDataForAuth(chunk) {
if (passwordDelivered || collectingPassword) return;
// After 1 s, any PTY output that looks like a shell prompt means we
// are already authenticated — cancel the password watch.
if (Date.now() - spawnTime > 1000) {
const str = Buffer.isBuffer(chunk) ? chunk.toString('utf8') : String(chunk);
// Shell prompts typically end with $, #, >, or % preceded by a space
if (/[$#>%]\s*$/.test(str)) {
cancelPasswordWatch();
return;
}
const str = Buffer.isBuffer(chunk) ? chunk.toString('utf8') : String(chunk);
outputSoFar += str;
seenOutput = true;
// If the accumulated output looks like post-login content, we are in —
// cancel the password watch immediately.
if (LOGIN_RE.test(outputSoFar)) {
cancelPasswordWatch();
return;
}
// Reset the quiet timer — show prompt once PTY has been silent 400 ms
// Reset the quiet timer — show prompt only after 600 ms of silence
clearTimeout(quietTimer);
quietTimer = setTimeout(startPasswordCollection, 400);
quietTimer = setTimeout(startPasswordCollection, 600);
}
pty.on('data', onPtyDataForAuth);
// Kick off the initial timer in case SSH produces no output at all
quietTimer = setTimeout(startPasswordCollection, 400);
// If SSH produces absolutely no output within 2 s (e.g. very slow tunnel)
// fall through to the password prompt anyway.
quietTimer = setTimeout(startPasswordCollection, 2000);
}
// PTY output → WebSocket (live, no buffering needed — browser is already open)