Tighten WebCRT command audit: reject device errors and stdout replay.

Only audit interactive commands from frontend audit_line with a CLI prompt prefix; skip prompt-sync Enter and device error echoes; join wrapped xterm rows for long commands.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-09-02 14:47:12 +08:00
parent 52424dce37
commit 302ebfac23
5 changed files with 163 additions and 27 deletions

View file

@ -102,13 +102,55 @@ export function isPromptOnlyLine(line: string): boolean {
return /^(?:[\w.-]+(?:\([^)]+\))*[#>]\s*|<[^>]+>\s*|\[[^\]]+\]\s*)$/.test(s);
}
const CLI_PROMPT_PREFIX = /^(?:[\w.-]+(?:\([^)]+\))*[#>]|>[\w.-]+|<[^>]+>|\[[^\]]+\])/;
export function hasCliPromptPrefix(line: string): boolean {
return CLI_PROMPT_PREFIX.test(normalizeAuditLine(line));
}
/** Device error/warning lines must never be audited as operator commands. */
export function isDeviceOutputLine(line: string): boolean {
const s = normalizeAuditLine(line).trim();
if (!s) return false;
const low = s.toLowerCase();
if (low.startsWith("%error") || low.startsWith("%warning")) return true;
if (low.includes("invalid input detected")) return true;
if (/^\^+\s*$/.test(s)) return true;
if (low.startsWith("enter configuration commands")) return true;
return false;
}
export function isAuditableCommandLine(line: string): boolean {
const s = normalizeAuditLine(line);
if (!s.trim() || isPromptOnlyLine(s) || isDeviceOutputLine(s)) return false;
return hasCliPromptPrefix(s);
}
function currentCommandLine(term: Terminal): string {
const buf = term.buffer.active;
const cur = buf.getLine(buf.cursorY);
if (cur) {
return normalizeAuditLine(cur.translateToString(true));
const y = buf.cursorY;
const rows: string[] = [];
let sawPrompt = false;
for (let i = y; i >= Math.max(0, y - 5); i -= 1) {
const text = normalizeAuditLine(buf.getLine(i)?.translateToString(true) ?? "");
if (!text.trim()) {
if (rows.length) break;
continue;
}
if (isDeviceOutputLine(text)) break;
rows.unshift(text);
if (hasCliPromptPrefix(text)) {
sawPrompt = true;
break;
}
}
return "";
if (!sawPrompt && rows.length === 0) {
const cur = buf.getLine(y);
return cur ? normalizeAuditLine(cur.translateToString(true)) : "";
}
return rows.join("").trimEnd();
}
function auditLineForEnter(term: Terminal | null, explicitLine?: string): string | undefined {
@ -118,7 +160,7 @@ function auditLineForEnter(term: Terminal | null, explicitLine?: string): string
: term
? currentCommandLine(term)
: "";
if (!raw.trim() || isPromptOnlyLine(raw)) return undefined;
if (!isAuditableCommandLine(raw)) return undefined;
return raw;
}