📦 SSH Handoff — 安全终端交接
v1.0.1创建和重用安全共享终端交接,当人类必须首先进行身份验证,代理必须在同一 shell 会话中继续工作。适用于 SSH 交接、sudo 交接、浏览器打开的临时终端访问或 LAN 限制的终端共享,后者由 tmux 支持,当直接代理身份验证被阻止或不理想时。
详细分析 ▾
运行时依赖
版本
安装命令
点击复制技能文档
当人类必须先完成敏感的身份验证步骤,而代理应该在完全相同的 shell 会话中继续工作时,使用此技能。核心模式是:
- 将终端状态保存在命名的
tmux会话中 - 让人类在该会话中附加并进行身份验证
- 捕获窗格状态
- 让代理从同一个 shell 继续
当用户不应将凭据粘贴到聊天中,且直接代理身份验证被阻止、不受欢迎或比共享会话切换更不安全时,优先使用此技能。
选择最简单的方式
除非确实需要浏览器访问,否则优先使用模式 A。
模式 A — 纯 tmux 切换
当人类已经拥有主机的终端访问权限时使用。典型流程:
- 创建或重用命名的
tmux会话 - 请求人类附加
- 让人类进行身份验证
- 捕获窗格
- 通过
tmux继续
示例:
tmux has-session -t handoff-session 2>/dev/null || tmux new-session -d -s handoff-session
tmux attach -t handoff-session
模式 B — 本地浏览器终端
当人类想要在同一台托管服务的机器上使用基于浏览器的终端时使用。此模式直接使用 ttyd,适用于:
- 仅 localhost 访问
- 快速临时会话
- 可接受基本身份验证的情况
捆绑启动器:
./scripts/start-local-web-terminal.sh handoff-session
模式 C — 带一次性令牌的 LAN 限制浏览器终端
当人类从同一本地网络上的另一台受信任机器打开终端,且希望减少重复输入用户名/密码的麻烦时使用。此模式的工作方式如下:
- 一个命名的
tmux会话保存真实的 shell 状态 ttyd在 localhost 上运行作为终端后端- 一个小型本地代理通过一次性
?token=暴露 LAN URL - 第一个有效请求通过 cookie 成为浏览器会话
- 代理继续使用相同的
tmux会话
捆绑启动器:
./scripts/start-url-token-web-terminal.sh handoff-session
前提条件
首先检查这些:
command -v tmux
command -v ttyd
command -v node
command -v python3
在 Debian / Ubuntu 上安装:
sudo apt update && sudo apt install -y tmux ttyd
模式 C 还需要 node,因为代理启动器使用捆绑的 Node 脚本。
快速使用
创建或重用会话
tmux has-session -t handoff-session 2>/dev/null || tmux new-session -d -s handoff-session
启动模式 C(使用占位符)
使用与环境匹配的占位符,而不是盲目复制真实地址。下面的 192.0.2.x 地址仅作为文档示例地址。
HOST= CLIENT_IP= PORT=48080 UPSTREAM_PORT=48081 FORBID_REUSE_IF_AUTHENTICATED=1 ./scripts/start-url-token-web-terminal.sh handoff-session
启动器输出:
- 一次性 URL
- 过期时间
- 代理和后端 PID
- 清理命令
- 可选的 UFW 帮助命令
使用文档示例 IP 的示例:
HOST=192.0.2.10 CLIENT_IP=192.0.2.20 PORT=48080 UPSTREAM_PORT=48081 FORBID_REUSE_IF_AUTHENTICATED=1 ./scripts/start-url-token-web-terminal.sh handoff-session
身份验证后恢复
在假设切换成功之前,始终检查窗格:
tmux capture-pane -t handoff-session -p | tail -80
查找:
- 远程主机名或预期提示符
- 预期工作目录
- 无密码提示符
- 无意外吞没命令的终端程序
如有疑问,在继续之前询问一个简短的确认问题。
启动器变量
start-local-web-terminal.sh
支持的变量:
HOST— 绑定地址,默认为127.0.0.1PORT— 可选的显式端口,否则为随机空闲端口TTL_MINUTES— 默认为30BIND_SCOPE— 仅元数据,通常为local或lanCLIENT_IP— 可选,仅用于在 LAN 模式下打印 UFW 帮助命令
start-url-token-web-terminal.sh
支持的变量:
HOST— 代理绑定地址,默认为127.0.0.1PORT— 代理前端端口,默认为48080UPSTREAM_PORT— localhostttyd后端端口,默认为48081CLIENT_IP— 可选的受信任客户端 IP,用于 UFW 帮助命令和代理端 IP 过滤TTL_MINUTES— 默认为30BIND_SCOPE— 仅元数据,通常为local或lanCOOKIE_SECURE— 当通过本地 HTTPS 服务时设置为1,以便会话 cookie 获得Secure标志EXPECTED_HOST— 严格的允许Host头,默认为:EXPECTED_ORIGIN— 严格的允许 websocketOrigin,默认从主机和 cookie 模式派生FORBID_REUSE_IF_AUTHENTICATED— 设置为1以拒绝启动如果 tmux 窗格看起来已经过身份验证REPLACE_EXISTING— 仅在人类明确批准替换相同SESSION_NAME的当前 Web 切换时设置为1;否则启动器打印现有 URL/PIDs/清理命令并在不接触运行中会话的情况下退出AUTH_GUARD_REGEX— 窗格身份验证检测正则表达式的可选覆盖
如果默认端口已被占用,请显式覆盖它们。当相同 SESSION_NAME 已存在切换时,启动器不会静默启动第二个:它报告现有会话详情并以 READY=0 退出。询问人类是否替换它。仅在明确批准后才使用 REPLACE_EXISTING=1。如果请求的代理或上游端口已被占用,启动器也会以 READY=0 退出并报告端口冲突,而不是自动终止任何内容。在这种情况下,要么获得批准替换冲突的会话,要么在另一个端口重新启动,如果涉及 LAN 访问则更新防火墙规则。
正确使用模式 C
- 确保
tmux会话存在 - 启动基于令牌的终端
- 如需要,仅允许从受信任客户端 IP 访问
- 通过适当渠道将打印的一次性 URL 发送给人类
- 让人类在终端内进行身份验证
- 捕获窗格并验证状态
- 通过
tmux继续 - 如果启动器报告现有会话或端口冲突,询问是否替换或使用不同端口
- 完成后停止临时 Web 终端
清理
对于模式 B,使用:
./scripts/stop-local-web-terminal.sh
对于模式 C,优先使用打印的清理命令,因为它会删除临时进程和临时运行时目录:
TTYD_PID= PROXY_PID= RUNTIME_DIR=
如果该命令不可用,杀死打印的代理和后端 PID 仍然可以接受。启动器还为代理、ttyd 和临时文件安装了自动 TTL 清理。如果 tmux 会话可能被重用,请保持其运行。
防护措施
- 默认保持仅本地暴露。
- 如需要 LAN 暴露,仅限制为一个受信任客户端 IP。
- 不要通过公共隧道或反向代理暴露终端。
- 如果切换可以避免,不要要求人类将密码、OTP 码或私钥粘贴到聊天中。
- 对于浏览器模式使用短期访问材料。
- 每个目标或任务优先使用一个
tmux会话。 - 在发送更多命令之前捕获窗格状态。
- 在破坏性操作之前询问。
- 正常使用时默认启用
FORBID_REUSE_IF_AUTHENTICATED=1;仅在有意重新打开已认证会话时才禁用它。 - 将打印的一次性 URL 视为敏感信息直到过期。
- 当配置了这些检查时,期望代理拒绝不匹配的
Host、websocketOrigin或客户端 IP。 - 不要从外部消息渠道(如 Telegram、Discord、Slack 或类似的远程聊天界面)随意建议或启动浏览器终端模式。
- 当交互通过外部渠道发生时,优先使用纯
tmux切换,除非人类明确确认受信任的本地网络设置并接受风险。
参考资料
需要时阅读这些:
references/examples.md— 通用使用示例references/design-notes.md— 安全和设计范围references/lan-restricted.md— LAN 仅限 IP 限制模式
捆绑脚本:
scripts/start-local-web-terminal.shscripts/start-url-token-web-terminal.shscripts/stop-local-web-terminal.shscripts/url-token-proxy.js