Files
NetMesh/domain/terminalPromptSecurity.test.ts
zhaolei 3c72efcb7f
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
[Init] Initial commit - NetMesh terminal manager
2026-09-13 18:24:01 +08:00

145 lines
5.7 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { createRequire } from 'node:module';
import assert from 'node:assert/strict';
import test from 'node:test';
import {
appendTerminalPromptSecurityTail,
isConfirmedTerminalShellPrompt,
isSensitiveTerminalChallenge,
isUntrustedTerminalInputPrompt,
shouldUsePluginTerminalCompletionProvider,
} from './terminalPromptSecurity.ts';
const require = createRequire(import.meta.url);
const sharedPromptSecurity = require('./terminalPromptSecurity.shared.cjs') as {
isUntrustedTerminalInputPrompt: typeof isUntrustedTerminalInputPrompt;
};
test('terminal prompt security recognizes password, MFA, OTP, PIN, and CJK challenges', () => {
const challenges = [
'Password: ',
'Enter passphrase> ',
'OTP> 123456',
'One-time password: 123456',
'Verification code> 123456',
'Duo passcode: 123456',
'MFA token: 123456',
'Authentication code: 123456',
'Security PIN: 123456',
'验证码> 123456',
'动态口令123456',
'二次认证密码123456',
];
for (const challenge of challenges) {
assert.equal(isSensitiveTerminalChallenge(challenge), true, challenge);
}
assert.equal(isSensitiveTerminalChallenge('password authentication succeeded'), false);
assert.equal(isSensitiveTerminalChallenge('echo OTP'), false);
});
test('terminal prompt security combines split output chunks within a bounded logical line', () => {
let tail = appendTerminalPromptSecurityTail('', '\u001b[33mVerifi');
tail = appendTerminalPromptSecurityTail(tail, 'cation code\u001b[0m> ');
assert.equal(isSensitiveTerminalChallenge(tail), true);
tail = appendTerminalPromptSecurityTail(tail, '\r\nuser@host:~$ ');
assert.equal(isSensitiveTerminalChallenge(tail), false);
assert.ok(tail.length <= 2_048);
});
test('terminal prompt security positively confirms shell prompts and fails closed on ambiguous prompts', () => {
const confirmed = [
'$ ',
'root# ',
'user@host:~/repo$ ',
'➜ ~/repo ',
'\uE0B0 ',
'PS C:\\Users\\alice> ',
'C:\\Users\\alice> ',
'user@host> ',
];
for (const prompt of confirmed) {
assert.equal(isConfirmedTerminalShellPrompt(prompt), true, prompt);
}
assert.equal(isConfirmedTerminalShellPrompt('OTP> '), false);
assert.equal(isConfirmedTerminalShellPrompt('custom> '), false);
assert.equal(isConfirmedTerminalShellPrompt('router> ', { allowHostStyleGreaterThan: true }), true);
});
test('plugin terminal completion policy requires a non-sensitive confirmed shell prompt', () => {
assert.equal(shouldUsePluginTerminalCompletionProvider({
sensitiveInputActive: false,
promptText: 'user@host:~$ ',
}), true);
assert.equal(shouldUsePluginTerminalCompletionProvider({
sensitiveInputActive: true,
promptText: 'user@host:~$ ',
}), false);
assert.equal(shouldUsePluginTerminalCompletionProvider({
sensitiveInputActive: false,
promptText: 'OTP> ',
}), false);
assert.equal(shouldUsePluginTerminalCompletionProvider({
sensitiveInputActive: false,
promptText: 'custom> ',
}), false);
});
test('unknown prompt-shaped authentication boundaries fail closed', () => {
assert.equal(isUntrustedTerminalInputPrompt('Custom authentication> '), true);
assert.equal(isUntrustedTerminalInputPrompt('Please authenticate: '), true);
assert.equal(isUntrustedTerminalInputPrompt('alice@host:~$ '), false);
assert.equal(isUntrustedTerminalInputPrompt('router> ', { allowHostStyleGreaterThan: true }), false);
assert.equal(isUntrustedTerminalInputPrompt('router> '), true);
});
test('ordinary shell commands ending with a colon are not untrusted input prompts (#2709)', () => {
// Broadcast pauses while passwordPromptActive is sticky. Typing `lsof -i:`
// must not trip the fail-closed colon heuristic, or peers miss the rest.
const commandLines = [
'user@host:~$ lsof -i:',
'bash-5.2$ lsof -i:',
'root# lsof -i:',
'PS C:\\Users\\alice> lsof -i:',
'C:\\Users\\alice> lsof -i:',
];
for (const line of commandLines) {
assert.equal(isUntrustedTerminalInputPrompt(line), false, line);
}
assert.equal(
isUntrustedTerminalInputPrompt('router> show ip:', { allowHostStyleGreaterThan: true }),
false,
);
// Bare host-style prompts stay fail-closed unless explicitly allowed.
assert.equal(isUntrustedTerminalInputPrompt('router> show ip:'), true);
// Mid-label `#` is not a prompt boundary (no whitespace after `#`).
assert.equal(isUntrustedTerminalInputPrompt('Challenge #1:'), true);
// Markers inside English challenge labels must not look like a shell PS1.
assert.equal(isUntrustedTerminalInputPrompt('Challenge # 1:'), true);
assert.equal(isUntrustedTerminalInputPrompt('Account $ code:'), true);
// Standalone auth-shaped lines without a confirmed shell prompt still fail closed.
assert.equal(isUntrustedTerminalInputPrompt('Please authenticate: '), true);
assert.equal(isUntrustedTerminalInputPrompt('Token: '), true);
assert.equal(isUntrustedTerminalInputPrompt('Password: '), true);
});
test('renderer and main-process prompt classifiers stay aligned', () => {
const cases: Array<[
string,
{ allowHostStyleGreaterThan?: boolean } | undefined,
boolean,
]> = [
['Custom authentication> ', undefined, true],
['Please authenticate: ', undefined, true],
['alice@host:~$ ', undefined, false],
['user@host:~$ lsof -i:', undefined, false],
['Challenge # 1:', undefined, true],
['router> show ip:', { allowHostStyleGreaterThan: true }, false],
['router> show ip:', undefined, true],
];
for (const [prompt, options, expected] of cases) {
assert.equal(isUntrustedTerminalInputPrompt(prompt, options), expected, prompt);
assert.equal(sharedPromptSecurity.isUntrustedTerminalInputPrompt(prompt, options), expected, prompt);
}
});