SSH 客户端能记下什么:回车审计和本地 JSONL

「SSH 终端是怎样做成的」第 10 篇。第 6 篇用 v1.2.26 说明客户端能看见什么。这一篇是 v1.2.38 补上的那一层:回车提交的一行,在本机留一条记录。依据:src/core/interactive_exec.rs、src/core/exec_history.rs、src/cli/ssh_session_log.rs、docs/tech/COMMAND-AUDIT.md、docs/tech/CLI-DESIGN.md。

要点

第 6 篇的结论还在。客户端拼出来的是一串按键,不是 shell 最终执行的命令,换一个 SSH 客户端就整个绕开。v1.2.38 可以在团队审计真的开着时,把这行追加到 ~/.mist/logs/exec-history.jsonl,一行一个 JSON。偏好可以关掉检查和写入。mist ssh --log-session 写的是另一个文件,而且只有你加上这个选项才会写。这些都不是强制。强制仍在服务端。

同一次回车,多写一条

v1.2.26 里,本地策略生效时,回车不会立刻送出 \r。客户端用上一次回车之后发出去的键拼出这一行,再问 CmdAuditEngine:拦截、确认,或放行。这一行不可信时——备用屏幕、文件传输、口令提示、缓冲跟踪不了的光标移动——回车照常发出。第 6 篇写的就是这道门。v1.2.38 没有换掉它。

加上去的是同一次路径上的一次写入,写进客户端本来就在用的那份结构化执行日志。没有第二套策略引擎。手输的一行要不要捕获,就是这个函数:

/// 偏好开,且至少一侧生效:本地团队策略,或这台主机的服务器审计。
/// 没登录团队时两侧都是 false:不拦截,也不写 JSONL。
pub fn interactive_capture_active(
    preference_on: bool,
    local_policy_active: bool,
    server_audit_for_host: bool,
) -> bool {
    preference_on && (local_policy_active || server_audit_for_host)
}

开关在偏好设置 → 操作审计 →「检查并记录终端里手输的命令」。默认开,旧的设置文件里没有这个字段时也按开处理。关掉之后,回车立刻发出,这条路径不再查规则,也不再为了收口去扫输出。

只开着还不够。客户端里的注释和函数是同一句话。没登录团队,两侧都是 false,手输不拦截,也不写这一行。登录了,但本地策略和这台主机的服务器审计 Agent 都没生效,同样不写。个人使用、勾选留在默认,键盘也不会把 exec-history.jsonl 撑大。

扣住回车,比写一行更窄。扣键(先按住 \r,再拦截 / 确认 / 放行)要偏好开、本地审计模块开、引擎里有生效的策略。如果生效的是这台主机的服务器 Agent,客户端可以不扣回车,仍然记下这一行。Agent 随后回来说拦住了,同一条记录改标成 blocked,退出码清掉。

文件里的一行

路径是 ~/.mist/logs/exec-history.jsonl。追加,一行一个 JSON,和批量执行用的是同一个 ExecLogRecord。交互标记是 interactive_pty,再加一个结果:allowed、alert、confirmed、blocked 或 cancelled。

拦住或取消的一行马上落盘,没有输出,也没有退出码。放行的一行先开着,直到下一行像提示符,或者出现 OSC 133 D。提示符是个启发式:较短的一行,以 $、#、% 或 > 结尾,正文里像路径或 user@host。不是 shell 语法分析。全屏程序、口令行、多行粘贴由调用方跳过,和第 6 篇那道门一样。方向键、Tab 补全、历史回呼仍然会把本地缓冲清掉或留空,这些回车也不进这条记录。键本身还是发给 shell。

写失败不会把会话弄断。目录建不出来,或者文件打不开,这次追加就丢掉。

别的路径本来就会往同一个文件追加(批量执行就是一条)。mist sop extract 读它,按最近几行草拟一份 Markdown。那是本机上的便利,不是团队审计档案,也不保证每台机器上的每次按键都在这里。

文件不是策略

JSONL 里没有任何东西能阻止命令执行。用户可以取消勾选,可以用 OpenSSH 连上去,也可以按 ↑ 调出一行——本地缓冲不跟踪这个动作,客户端会把回车放过去,而不是去拦一条它看不见的命令。第 6 篇的服务端路径没变:非交互命令走 sshd 的 ForceCommand,交互 shell 里可选的 bash DEBUG trap 用户自己就能卸掉。一条命令如果必须不跑,决定仍然得发生在进程被拉起来的地方。

mist ssh --log-session

这个选项是另开的,不加就不记。不加就不会建会话文件,也不会去扫命令输出。桌面里的那个勾选对它不起作用。

mist ssh web-01 --log-session
mist ssh web-01 --log-session=/tmp/web-01.jsonl

默认路径是 ~/.mist/logs/ssh-<会话>-<UTC时间>.jsonl。会话名用已保存会话的名字;临时地址则是 user@host[:port]。字母、数字、_ 和 - 以外的字符折成 -,这一段还有长度上限。时间戳是 UTC 的 YYYYMMDDTHHMMSSZ。进入 raw mode 之前,CLI 把路径打到 stderr([mist] command log: …,中文环境是「命令日志」)。

记录类型还是 ExecLogRecord。标签是 interactive_pty 加结果,再加 cli_ssh。写到这次会话自己的文件,不追加到 exec-history.jsonl。exit_code 仍然只有 OSC 133 D 才有。口令行跳过。方向键、历史回呼和 Tab 会清掉本地这一行,这些回车不记。shell 还是收到了键。

如果载入了团队策略,mist ssh 在回车时走和桌面一样的检查:拦截、确认或放行。拦住就发 Ctrl-U,在 stderr 上告诉你,并写一条标成 blocked 的记录。这仍是客户端。另一份 ssh 根本看不见。

小结

常见问题

MistTerm 会把手输命令都写进 exec-history.jsonl 吗?

不会。从 v1.2.38 起,偏好开着,并且团队审计至少有一侧生效(本地策略,或这台主机的服务器审计),才会追加一行。没登录团队时,勾选还在也不会为手输命令写这个文件。

mist ssh --log-session 写到哪里?

写到单独的文件,默认 ~/.mist/logs/ssh-<会话>-<UTC时间>.jsonl,不追加到 exec-history.jsonl。不加这个选项就不会建文件,也不会去扫命令输出。

客户端这份 JSONL 能当作命令策略的强制手段吗?

不能。用户可以关掉偏好,可以换 OpenSSH,也可以提交一行本地缓冲重建不出来的命令。强制仍在服务端,例如 sshd 的 ForceCommand,见第 6 篇。

相关文章

← 第 9 篇 · 系列目录