fix(cli): prevent terminal response leakage on high-latency SSH

When SSHing into a VM with network latency, terminal responses to startup
queries (kitty protocol detection) can arrive after the 200ms timeout expires.
The original code immediately restored raw mode, causing late responses to
leak through as visible text.

This fix adds two layers of defense:
1. Drain handler in kittyProtocolDetector: Adds a 100ms window after timeout
   to silently consume late-arriving responses
2. Regex filter in KeypressContext: Catches any terminal response patterns
   that make it past the detection phase, regardless of timing

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
tanzhenxin 2026-03-27 12:08:05 +00:00
parent 070ec5b43e
commit d6f5d9997f
2 changed files with 23 additions and 5 deletions

View file

@ -540,7 +540,16 @@ export function KeypressProvider({
}
};
// Matches terminal query responses (DA1, DA2, Kitty protocol query)
// that may arrive late from startup detection in kittyProtocolDetector.
// These are never valid user input.
// eslint-disable-next-line no-control-regex
const TERMINAL_RESPONSE_RE = /^\x1b\[[?>][\d;]*[uc]$/;
const handleKeypress = async (_: unknown, key: Key) => {
if (TERMINAL_RESPONSE_RE.test(key.sequence)) {
return;
}
if (key.sequence === FOCUS_IN || key.sequence === FOCUS_OUT) {
return;
}

View file

@ -37,11 +37,20 @@ export async function detectAndEnableKittyProtocol(): Promise<boolean> {
const onTimeout = () => {
timeoutId = undefined;
process.stdin.removeListener('data', handleData);
if (!originalRawMode) {
process.stdin.setRawMode(false);
}
detectionComplete = true;
resolve(false);
// Keep a drain handler briefly to consume any late-arriving terminal
// responses that would otherwise leak into the application input.
const drainHandler = () => {};
process.stdin.on('data', drainHandler);
setTimeout(() => {
process.stdin.removeListener('data', drainHandler);
if (!originalRawMode) {
process.stdin.setRawMode(false);
}
detectionComplete = true;
resolve(false);
}, 100);
};
const handleData = (data: Buffer) => {