+36
-18
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user