决策日期:2026-07-30
状态:已实现并封板;本文保留为技术方案与反方评审记录,当前状态以 TODO.md 为准
产品目标见 product-direction.md,稳定工程约束见 DESIGN.md,当前状态见 TODO.md 本方案只覆盖 PRODUCT-1,不实现 Quick Check、Incident、Repair、持续调度或自动修复
PRODUCT-1 的目标不是实现 SSH 管理平台,而是把下面两种状态可靠地连接起来:
用户已经可以通过 OpenSSH 连接自己的服务器
-> Clawkit 确认目标、身份、主机指纹和远端能力
-> 建立受限只读 MCP 会话
-> 后续 Quick Check 和 Ops Loop 可以安全使用
首个成功路径固定为:
/remote add --from-ssh <alias>
-> 预览目标和只读能力
-> 用户确认登记
-> /remote doctor
-> /remote connect
-> READY
实现原则:
- 使用系统 OpenSSH,不引入 Java SSH 协议栈;
- 保存 SSH alias,不缓存展开后的 host、key path 和 known_hosts 路径;
ssh -G只用于选中目标后的确定性检查,不用来重新实现 OpenSSH;- 用户选择人类可读的能力包,严格 hash 由内置 manifest 提供;
- doctor 不调用模型、不调用业务工具、不挂载 ToolRegistry;
- 新 host key 不自动接受,变化的 host key 硬阻断;
- 远端安装只提供可审查、可验证、可撤销的显式步骤,不静默执行 sudo;
- REMOTE-0 的 attestation、generation-bound mount、ToolExecution 链和 legacy YAML 继续复用。
- 本地 Windows、macOS、Linux;
- 系统 OpenSSH 客户端;
- 默认用户 SSH config 中的明确
Hostalias; Include中的明确 alias;- SSH Agent、多个 IdentityFile、SSH certificate;
ProxyJump;- 单活动目标;
- 远端 Linux + Docker Compose;
- 已部署的 opsro forced-command MCP;
APP_DOWN_V1与POSTGRES_DIAGNOSIS_V1两个只读 profile;- 原有显式 endpoint YAML 作为高级兼容入口。
- 任意 IP/hostname 由模型直接发起连接;
- SSH shell、SFTP、SCP、端口转发;
- 密码和 keyboard-interactive 认证;
- 在 Clawkit 内输入或保存私钥口令;
- 自定义
ProxyCommand、KnownHostsCommand、Match exec; - 自动接受或自动替换 host key;
- 自动生成、托管或轮换用户私钥;
- Clawkit 静默执行 sudo、修改 sshd 或安装系统包;
- 多活动目标、多主机并发、团队共享;
- opsfix 写能力;
- 普通 Linux 进程型服务。
最后一项是当前事实边界:现有远端实现主要依赖 Docker backend。产品文案不能在 PRODUCT-1 阶段宣称已经支持普通进程服务。
RemoteMcpSession已完成 SSH/MCP 生命周期与严格 attestation;RemoteTargetDescriptor已固定 server、protocol、probe、profile、toolSetHash 和 contractHash;RemoteConnectionService已完成单活动目标、状态机、generation 和原子 mount;TargetBoundToolAdapter已完成 target/generation 绑定与远端输出脱敏;FileRemoteTargetStore已有原子持久化;RemoteIntentRouter只允许已登记 target;Remote0CliE2ETest已覆盖真实 ToolRegistry -> ToolExecution 链。
当前 RemoteEndpointConfig 把 OpenSSH 配置拆成 host、port、user、identityFile 和 known_hosts,再重新拼装命令。这会丢失 Include、ProxyJump、Agent、certificate、HostKeyAlias 等行为。
当前 RemoteTargetConfig 要求普通用户填写 endpoint 和完整 attestation hash。
当前 RemoteCommandHandler 使用手写字符串切分,并把 generation、hash 和完整工具列表放进默认状态。
当前 StdioTransport 会把子进程 stderr 原样写入日志;SSH stderr 可能包含主机名、用户名和本地密钥路径。
当前 DefaultProcessRunner 的环境白名单不包含 SSH_AUTH_SOCK 和 Windows USERPROFILE,不能直接用于 Agent/SSH doctor。
当前 setup 脚本属于 Fixture/交付脚本:需要 root、手动上传文件并修改 sudoers/sshd,不是产品级安装入口。
flowchart TB
CMD["RemoteCommandHandler"] --> PARSER["RemoteCommandParser"]
CMD --> ONBOARD["RemoteOnboardingService"]
CMD --> DOCTOR["RemoteDoctorService"]
CMD --> CONN["RemoteConnectionService"]
ONBOARD --> DISCOVERY["SshTargetDiscovery"]
ONBOARD --> CATALOG["RemoteProfileCatalog"]
ONBOARD --> STORE["RemoteTargetStore"]
DISCOVERY --> SSH["SystemOpenSshFacade"]
DOCTOR --> SSH
DOCTOR --> RESOLVER["RemoteTargetResolver"]
DOCTOR --> PROBE["RemoteMcpSession · probe only"]
CONN --> RESOLVER
RESOLVER --> CATALOG
RESOLVER --> SPEC["RemoteSshConnectionSpec"]
CONN --> SESSION["RemoteMcpSession"]
SESSION --> MOUNT["ToolRegistry mount"]
职责边界:
| 组件 | 模块 | 职责 |
|---|---|---|
RemoteCommandParser |
cli | 解析确定性 /remote 子命令,不执行行为 |
RemoteOnboardingService |
cli | 发现、预览、确认、登记目标 |
SshTargetDiscovery |
cli | 静态读取明确 Host alias,不建立网络连接 |
SystemOpenSshFacade |
cli/tools | 执行有界 ssh -V/-G、ssh-add -l 等本地探针 |
RemoteDoctorService |
cli | 编排分阶段检查,生成结构化报告 |
RemoteProfileCatalog |
cli | 提供版本化只读 profile manifest |
RemoteTargetResolver |
cli | 把持久 registration + manifest 解析为 tools 层运行时类型 |
RemoteSshConnectionSpec |
tools | 为 explicit endpoint 和 OpenSSH alias 提供统一 SSH 启动契约 |
RemoteMcpSession |
tools | 保持现有 MCP handshake、attestation 和调用语义 |
不新增 Maven 模块,不让 clawkit-tools 依赖 CLI 或 Ops。
新增 schema v2:
schemaVersion: 2
targetId: test-server
connection:
type: openssh-alias
alias: test-server
remoteUser: opsro
profileManifestId: app-down-readonly-v1持久化字段只包含:
targetIdconnection.typeconnection.alias- 固定受限身份
remoteUser profileManifestId
不得包含:
- 私钥内容或口令;
- identity path;
- 展开后的 host/IP;
- known_hosts 内容;
- SSH Agent socket;
- toolSetHash/contractHash;
- doctor 原始 stderr。
建议领域类型:
public record RemoteTargetRegistration(
int schemaVersion,
String targetId,
RemoteConnectionReference connection,
String profileManifestId
) {}
public sealed interface RemoteConnectionReference
permits OpenSshAliasReference, LegacyExplicitEndpointReference {}
public record OpenSshAliasReference(
String alias,
String remoteUser
) implements RemoteConnectionReference {}remoteUser 不由模型设置。内置 opsro manifest 默认要求 opsro;命令行不能改成 root。
在 clawkit-tools 新增窄接口:
public interface RemoteSshConnectionSpec {
List<String> sshArgs();
String safeRef();
Duration connectTimeout();
Duration requestTimeout();
int maxOutputBytes();
}- 原
RemoteEndpointConfig实现该接口,作为 legacy explicit 模式; - 新增
OpenSshAliasConnectionSpec; RemoteMcpSession字段和构造器改为消费接口;- 保留接收
RemoteEndpointConfig的兼容构造器或让现有调用通过接口自然编译; - 不复制第二套 MCP session。
public record RemoteProfileManifest(
int schemaVersion,
String manifestId,
String displayName,
String serverName,
String protocolVersion,
String probeVersion,
String capabilityProfile,
String expectedToolSetHash,
String expectedToolContractHash,
RemoteAccessMode accessMode,
String requiredRemoteUser
) {}首版 catalog 只包含:
APP_DOWN_V1POSTGRES_DIAGNOSIS_V1
不得包含 FIX_ORDER_API_V1。
Catalog 放在 clawkit-cli 的 classpath resource 或不可变常量中;CLI 生产代码不依赖 clawkit-ops-mcp。增加 test-scope contract test,直接使用 OpsMcpServer 计算结果核对 catalog,防止服务端 schema 漂移后客户端常量未更新。
连接时保留 alias:
ssh <Clawkit强制安全参数> -l opsro <alias>
不能执行:
ssh -i <展开key> -p <展开port> <展开user>@<展开host>
原因是后一种方式丢失 OpenSSH 的实际配置语义。
Legacy explicit endpoint 则采用相反策略:既然 host、port、identity 和
known_hosts 都由目标契约显式给出,就必须加 -F none,完全禁止读取用户级和
系统级 SSH config,避免一个看似普通的 hostname 命中 ProxyCommand、Match exec 或其他本地配置。OpenSSH 官方手册明确说明 -F none 表示不读取任何配置
文件,-G 会在求值 Host 和 Match 后输出最终配置:
ssh(1)。
所有连接模式共用一个 RemoteSshSafetyPolicy,至少强制:
BatchMode=yesPasswordAuthentication=noKbdInteractiveAuthentication=noPreferredAuthentications=publickeyStrictHostKeyChecking=yesRequestTTY=no/-TClearAllForwardings=yesForwardAgent=noForwardX11=noPermitLocalCommand=noControlMaster=noControlPath=noneControlPersist=noTunnel=noAddKeysToAgent=no- 有界 ConnectTimeout、ServerAliveInterval、ServerAliveCountMax
参数必须位于 destination 之前,并用行为测试断言最终 argv。不得使用 shell 字符串拼接。
Alias 需要通过窄字符校验:
[A-Za-z0-9][A-Za-z0-9._:-]{0,127}
拒绝前导 -、空白、控制字符和换行,避免被解释为 ssh option。
Alias 模式不强制:
-iIdentitiesOnly=yes
否则会破坏 Agent、certificate 和多 IdentityFile。
SSH 子进程环境改为显式白名单,至少保留:
PATHHOMEUSERPROFILEHOMEDRIVEHOMEPATHSYSTEMROOTCOMSPECSSH_AUTH_SOCKSSH_AGENT_PIDLANGLC_ALLTEMP/TMP/TMPDIR
不把应用 Token、模型 Key、Webhook 或任意 .env 变量传给 SSH/ProxyJump 子进程。
自定义 ${ENV} SSH 配置只有变量在安全白名单中才受支持;doctor 应把其他变量导致的解析问题报告为 SSH_CONFIG_UNSUPPORTED。
首版允许 ProxyJump,拒绝:
ProxyCommandKnownHostsCommandLocalCommandRemoteCommandMatch exec
理由:
- 这些选项可能在 Clawkit 连接或
ssh -G时执行本地/远端命令; - 用户登记一次目标后,后续自然语言连接可能间接再次触发命令;
- PRODUCT-1 的授权范围只是连接预定义远端能力,不等于授权任意本地命令。
Alias 模式在执行任何 ssh -G 或真实 SSH 前,必须先静态审计用户级、系统级和
全部 Include 形成的配置图。首版采取保守策略:配置图任一可读取文件出现上述
命令型指令,或 Include 无法完整展开,就不调用 ssh,直接 fail closed。该限制也
覆盖 ProxyJump 可能引用的跳板 alias。这样会拒绝一部分本可安全求值的复杂配置,
但不会为了“智能判断 Match 是否生效”而重新实现 OpenSSH 解释器。
Legacy explicit 模式不做这套推断,统一使用 -F none 隔离配置。
OpenSSH 明确定义 ProxyCommand 可执行任意 shell 命令,KnownHostsCommand
会在连接期间执行,Match exec 会执行命令来决定条件是否成立:
ssh_config(5)。
发现这些配置时 doctor 提示用户创建一个只使用 HostName/User/IdentityFile/ProxyJump 的专用、独立 config;首版不提供“我确认 仍然执行”的旁路。
SshTargetDiscovery:
- 定位默认用户 SSH config;
- 同时定位平台默认的系统级 client config;
- 递归处理
Include,设置最大深度和文件数; - 只读取普通文件;
- 排除符号链接循环;
- 先扫描命令型指令;命中或无法完整扫描则将 alias 集合标记为不安全;
- 提取不含 wildcard 和 negation 的明确 Host token;
- 去重但保留来源文件和行号;
- 不执行网络请求;
- 不执行
ssh -G; - 不把 HostName、User、IdentityFile 放入模型上下文。
列表展示 alias,不默认展示真实 IP。
用户显式选择 alias,且配置图静态安全预检通过后,才运行:
ssh <相同安全覆盖> -G -l opsro <alias>
只解析 allowlist 字段:
- hostname
- port
- user
- proxyjump
- hostkeyalias
- userknownhostsfile 是否存在
- identityfile 数量
- identityagent 是否启用
- 被禁止配置是否存在
原始 ssh -G 输出:
- 只保留在有界内存中;
- 不写日志;
- 不进入 RunEvent、Session、Memory 或模型;
- 对外只返回结构化、安全摘要。
ssh -G 不是静态安全预检的替代品,因为它会求值 Match;顺序必须固定为:
静态扫描配置图
-> 发现命令型指令/扫描不完整:拒绝,且不启动 ssh
-> 通过:对用户选中的 alias 执行 ssh -G
-> allowlist 解析与二次检查
-> 用户确认
/remote add
发现 3 个 SSH 目标:
1. test-server
2. staging
3. db-lab
请选择目标:
非交互入口:
/remote add --from-ssh test-server
/remote add --from-ssh test-server --as order-api-test
targetId 默认来自 alias 的规范化结果;发生冲突时必须明确要求 --as,不能静默覆盖。
始终使用:
StrictHostKeyChecking=yes
不使用:
nooffaccept-new- 自动
ssh-keyscan >> known_hosts
静态检查只能判断 known_hosts 是否存在候选记录;最终结论以真实 SSH 握手为准。
结果必须区分:
| 状态 | 行为 |
|---|---|
TRUSTED |
继续 |
UNKNOWN |
阻止;要求用户通过普通 SSH 或云控制台核对 fingerprint |
CHANGED |
硬阻断;不提供一键删除旧记录 |
UNVERIFIABLE |
阻止;展示安全检查步骤 |
ssh-keyscan 只能用于展示“远端当前声称的 key”,不能作为真实性证明,也不能自动写文件。
public record RemoteDoctorReport(
String targetId,
DoctorOverallStatus status,
List<RemoteDoctorCheck> checks,
Instant startedAt,
Instant completedAt
) {}
public record RemoteDoctorCheck(
DoctorStage stage,
DoctorCheckStatus status,
String code,
String summary,
String nextAction,
Map<String, String> safeDetails,
Duration duration
) {}状态:
PASSWARNFAILSKIPPED
Doctor stage:
OPENSSHSSH_CONFIGSSH_SAFETYHOST_IDENTITYSSH_AGENTSSH_TRANSPORTREMOTE_MCPATTESTATIONCLEANUP
- 不调用 LLM;
- 不调用
tools/call; - 不向 ToolRegistry mount;
- 可以建立临时
RemoteMcpSession完成 initialize 和 tools/list; - 必须 try-with-resources 关闭 session;
- 前置失败后,依赖该前置的检查标记
SKIPPED; - Agent 检查只是提示,实际 SSH 握手才是认证事实;
- 不因
ssh-add -l有 identity 就宣称目标认证一定成功; - 重复运行不得改变 target、known_hosts、Agent 或远端状态。
默认:
[test-server · read-only]
✓ OpenSSH 可用
✓ SSH config test-server -> example.com:22
✓ Host identity 已信任
✓ Authentication 公钥认证成功
✓ Remote service clawkit-ops-mcp
✓ Capability Linux 服务基础检查
✓ Contract 5 个只读工具,合同匹配
READY:可以连接 test-server
失败:
[test-server · disconnected]
✗ Authentication 公钥认证失败
影响:Clawkit 尚未连接,也没有挂载远程工具。
下一步:
1. 先运行 ssh test-server,确认原有 SSH 配置;
2. 如果密钥有口令,把密钥加入 ssh-agent;
3. 修复后重新运行 /remote doctor test-server。
详情:RMT-005
高级入口:
/remote doctor test-server --verbose
/remote doctor test-server --json
JSON 输出保持稳定 schema;存在 FAIL 时命令语义为失败。REPL 内不退出应用,未来非交互 CLI 应返回非零 exit code。
保留 RMT-001~015,补充或细化:
| code | 含义 |
|---|---|
RMT-004 |
host key unknown/rejected/changed,reason 放 safeDetails |
RMT-005 |
SSH public-key authentication failed |
RMT-006 |
DNS/TCP/SSH transport unreachable |
RMT-007 |
MCP lifecycle/protocol failure |
RMT-016 |
SSH config unsupported or unsafe |
RMT-017 |
SSH Agent unavailable/no identity |
RMT-018 |
remote component unavailable |
RMT-019 |
profile manifest not found/unsupported |
错误分类以阶段、exit code 和有限 stderr pattern 综合判断。不能匹配时返回保守的 SSH_FAILED/REMOTE_MCP_FAILED,不得猜测为“远端未安装”。
修改 StdioTransport 的诊断边界:
- 允许注入
TransportDiagnosticSanitizer或等价窄接口; - 默认构造器保持兼容;
- SSH transport 必须在日志写入前脱敏;
- 外部 getter 只返回脱敏、有界诊断;
- 原始 stderr 只允许在进程生命周期内用于分类,不落盘、不进模型;
- 不在 INFO 日志打印每一行 SSH stderr;正常连接只记摘要。
脱敏至少处理:
- Windows/Unix 用户目录;
- identity/key/certificate path;
- username@hostname;
- IP 和内部 hostname 的展示策略;
SSH_AUTH_SOCK;- ProxyJump 链;
- 可能出现在错误中的环境变量值。
目标命令:
/remote add
/remote add --from-ssh <alias> [--as <targetId>]
/remote add <targetId> --config <legacyYaml>
/remote list
/remote status
/remote inspect [targetId]
/remote doctor <targetId> [--verbose|--json]
/remote connect <targetId>
/remote disconnect
/remote remove <targetId>
兼容:
/remote show可以暂时作为inspectalias;- 原
/remote add <id> --config <file>保留; - 自然语言连接只选择已登记 target。
不要继续用 split("\\s+", 3) + replace("--config", "")。新增确定性 parser,复用现有 JLine/Picocli 能力,并通过接口注入交互选择器,测试不得直接依赖真实 System.in。
默认 status 只展示:
- 当前 target;
- state;
- read-only/write scope;
- 能力摘要;
- 最近错误及下一步。
inspect 才展示:
- serverName;
- protocol/probe/profile;
- generation;
- toolSetHash/contractHash;
- 工具名;
- 连接与 attestation latency;
- 安全覆盖和 SSH config 来源摘要。
- schema v1 继续读取和连接;
- 新向导只写 schema v2;
- 一个 store 可以同时存在 v1/v2 target;
- 不在启动时自动重写用户文件;
- 提供显式迁移命令之前,不删除 v1 字段;
- v1 和 v2 都使用同一
RemoteConnectionService、RemoteMcpSession和安全覆盖。
增加直接登记领域对象的方法:
void add(RemoteTargetRegistration registration, boolean replace);原 add(targetId, configFile, replace) 保留为 legacy adapter。向导不得为了调用旧 API 创建临时 YAML。
持久化继续:
- sibling temp file;
- atomic replace;
- 单个坏 target 不阻断其他 target;
- 错误日志不打印完整 entry。
PRODUCT-1 的主要验收基于“远端已经准备好”。安装工作单独放在最后一个切片。
安装引导应提供:
prepare / dry-run
-> 展示将创建的用户、文件、sudoers 和 sshd 变化
-> 用户自行确认并在管理员终端执行
-> verify
-> doctor
-> revoke
约束:
- 不在
/remote add中自动执行 sudo; - 不在模型工具中暴露 setup/revoke;
- 安装包有版本和 SHA-256;
- setup、verify、revoke 幂等;
- 始终要求保留第二管理员通道;
- 修改前备份,配置校验失败自动回滚;
- 不自动修改全局
PermitUserEnvironment;其安全默认值无需为了 opsro 重写全局配置; authorized_keys restrict,command=...与 sshd Match 双层限制继续保留;- opsro 可以复用用户明确选择的公钥;opsfix 继续使用独立写身份和独立密钥;
- setup 只支持文档声明的 Linux/Docker 环境。
现有 ops-fixtures/remote 脚本继续服务 E2E。产品安装资产在完成审计后再进入 release packaging,不能让正式 CLI 运行时依赖 fixture 目录。
修改:
- 新增
RemoteSshConnectionSpec RemoteEndpointConfig实现接口- 新增
OpenSshAliasConnectionSpec - 新增共享
RemoteSshSafetyPolicy RemoteMcpSession改用接口- SSH transport stderr 脱敏
- SSH 子进程环境白名单补全 Agent/Windows 变量
门禁:
- 现有 REMOTE-0 E2E 不变;
- exact argv 测试;
- alias 注入负例;
- legacy exact argv 含
-F none; - Agent 环境保留;
- 禁止转发/TTY/密码的机械断言;
- stderr 不泄露本地路径。
修改:
RemoteTargetRegistration- v1/v2 persistence DTO
RemoteProfileManifest/CatalogRemoteTargetResolver- Store 直接 add API
- catalog 与 OpsMcpServer cross-contract test
门禁:
- v1 文件继续读取;
- v2 不包含 key path/hash;
- FIX profile 不可选择;
- manifest 漂移 fail closed;
- mixed v1/v2 store 原子读写。
修改:
SystemOpenSshFacadeSshTargetDiscoveryRemoteCommandParserRemoteOnboardingService/remote add --from-ssh- 交互选择
门禁:
- Include、重复、注释、引号、Windows path;
- wildcard/negation 不列出;
- Include 循环与深度限制;
- 用户级/系统级/Include 配置图的命令型指令拒绝;
- 命中
Match exec时证明未启动ssh -G; - ProxyJump 引用的 alias 不能绕过静态审计;
- 发现阶段零网络、零命令执行;
- 明确确认前不写 store。
修改:
RemoteDoctorService- doctor DTO/renderer
- SSH error classifier
/remote doctor- 简化 status/list
- 新增 inspect
门禁:
- doctor 零 tool call、零 mount;
- 每种失败有 FAIL/SKIPPED 顺序和 nextAction;
- unknown/changed host key 区分;
- auth/network/MCP/attestation 区分;
- 失败后进程、session、mount、active target 全部清理;
- JSON schema 稳定。
修改:
- 用户文档;
- versioned setup/verify/revoke release assets;
- doctor 安装缺失提示;
- Windows/Linux 本地 smoke;
- 首次连接产品 E2E。
门禁:
- 无静默 sudo;
- setup 完整命令可审查;
- verify/revoke 通过;
- 从已有 SSH alias 到首次 READY 的操作记录和耗时达标。
每个 PR 必须独立编译、测试和可回滚。不得把五个切片一次性提交。
- alias/targetId 校验;
- SSH argv 与安全覆盖;
- profile catalog;
- v1/v2 schema;
- ssh -G allowlist parser;
- alias discovery;
- stderr 分类与脱敏;
- doctor 状态归并;
- status/inspect renderer。
- fake
ProcessRunner驱动 OpenSSH facade; - fake MCP transport 驱动 doctor;
- Store mixed schema 与原子写;
- RemoteTargetResolver 生成正确 descriptor/spec;
- Doctor 不调用 ToolRegistry.mount;
- 向导确认/取消/冲突处理。
- 临时 SSH config 覆盖 Include、ProxyJump、Agent、certificate;
- 本地测试 sshd 覆盖 known/unknown/changed key;
- 认证失败、连接拒绝、超时、远端进程早退;
- 错误 hash/profile/tool annotation;
- connect/disconnect 后 generation 与 registry;
- CLI 从 parser 到 service 的真实链路。
- Windows 本地 OpenSSH + Linux 远端;
- Linux 本地 OpenSSH + Linux 远端;
- 已准备 APP_DOWN_V1;
- 已准备 POSTGRES_DIAGNOSIS_V1;
- SSH Agent 中的带口令密钥;
- ProxyJump;
- doctor -> connect -> 两个只读 ToolExecution -> disconnect;
- 无残留 SSH 进程、mount、active target;
- profile 不被 E2E 留在错误状态。
普通 CI 不连接真实网络;真实 E2E 继续使用 group 隔离。
- 已准备服务器从
/remote add到 READY 不超过 3 分钟; - 主路径不手写 YAML、host、port、key path、known_hosts 或 hash;
- 不超过 3 个主要用户动作;
- 所有远程输出显示 target;
- 用户默认不看到 generation/hash;
- 已知错误 100% 有原因、影响和下一步;
- unknown host key 不自动接受;
- changed host key 硬阻断;
- Agent/ProxyJump 可用;
- 旧 YAML/E2E 不退化;
- 连接失败退出后无资源残留;
- 无私钥、口令、Agent socket、原始 SSH config 或 stderr 泄露;
- 安装缺失时用户知道下一步,但 Clawkit 未执行 sudo。
以下从“方案为什么不应该通过”的角度审查。
问题成立。ProxyCommand、KnownHostsCommand、LocalCommand 和 Match exec 都可能执行命令。用户登记 target 后,模型可以通过自然语言连接已登记目标,间接再次触发这些命令。
修正:
- 首版只支持 ProxyJump;
- 静态发现不运行 ssh;
- 任何
ssh -G前先静态审计用户级、系统级和 Include 配置图; - 图中任一命令型指令或无法读取的 Include 都 fail closed;
- legacy explicit endpoint 使用
-F none; PermitLocalCommand=no和清除 forwarding;- registration 是确定性、显式用户动作;
- SSH 环境只传安全白名单。
结论:修正后可接受。
问题成立。Match 条件可能触发 exec,而且展开结果可能包含本地路径和命令。
修正:
- 不在枚举阶段批量执行
ssh -G; - 对用户选中的 alias 且静态审计通过后才运行;
- 静态扫描发现
Match exec时不启动 ssh; - 原始输出只在有界内存解析,不写日志/模型。
结论:修正后可接受,但需要专门负例测试。
问题部分成立。OpenSSH 多数单值配置采用 first value wins,命令行优先;但 IdentityFile 和 forwarding 等存在累积语义。
修正:
- 用
ClearAllForwardings=yes明确清除累计 forwarding; - 禁止 Agent/X11/Tunnel/LocalCommand/TTY;
- exact argv + 临时 ssh config 行为测试;
- legacy 和 alias 模式必须共用一套 safety policy。
结论:只有机械测试证明后才能合入。
问题成立。ssh-add -l 只能证明 Agent 可达和存在 identity,不能证明该 identity 被目标接受。
修正:
- Agent stage 只输出 readiness;
- Authentication PASS 只能来自真实 SSH 握手;
- 无 Agent 但无口令 identity 可用时允许真实连接成功;
- 不根据 Agent 状态跳过握手。
结论:修正后可接受。
问题成立。若没有服务端交叉验证,catalog 很快漂移。
修正:
- CLI production 不依赖 ops-mcp;
- test scope contract test 直接比较
OpsMcpServer计算结果; - manifest 变化必须与 server schema 同一提交;
- 未知 manifest/profile fail closed;
- 不从远端首次响应自动学习 hash。
结论:必须把 cross-contract test 设为合入门禁。
问题部分成立。它会启动 SSH 和远端 forced-command MCP 进程,虽然不应改变业务状态。
修正:
- doctor 定义为控制面只读,不定义为“零进程”;
- 只允许 initialize、initialized、tools/list、ping;
- 禁止 tools/call 和 ToolRegistry mount;
- 每次 probe 都关闭 transport;
- 服务端仍只暴露只读 profile。
结论:修正后符合产品边界。
问题成立。SSH exit code 255 不能细分所有失败,stderr 文本也不是稳定协议。
修正:
- 先用 doctor stage 缩小范围;
- pattern 只识别少量稳定已知错误;
- 未识别时返回保守通用错误;
- 不因模糊文本宣称“远端未安装”;
--verbose只显示脱敏诊断。
结论:可接受,但不能承诺 100% 精准根因。
问题成立,并且是 P0 阻断项。当前 stderr 会逐行 INFO 落日志。
修正:
- PR-1 先建立 diagnostic sanitizer;
- SSH stderr 写日志前脱敏;
- raw stderr 不对外暴露;
- 日志泄露测试必须覆盖 Windows/Unix key path、host、Agent socket。
结论:该修复必须早于 alias 正式路径。
问题成立。当前默认白名单缺少 SSH_AUTH_SOCK 和 USERPROFILE;而 StdioTransport 又继承过多环境,两条路径不一致。
修正:
- 定义唯一 SSH 环境白名单;
- doctor 和正式连接共用;
- 补 Agent/Windows 变量;
- 排除 Provider/API secret;
- 用 fake env 和真实 Agent E2E 验证。
结论:必须在 PR-1 解决。
问题成立。即使两个 profile 都只读,POSTGRES profile 也暴露更多数据库证据。
修正:
- 默认选择最小 APP_DOWN 只读包;
- 多 profile 时向用户展示人类可读能力范围;
- 不能根据远端实际返回自动升级到更宽 profile;
- profile 变化需要显式确定性操作;
- FIX profile 不进入 PRODUCT-1 catalog。
结论:修正后可接受。
问题成立。远端安装涉及 root、sudoers、sshd、Java、Docker 和回滚,远比“连接向导”风险高。
修正:
- 首个验收以已准备远端为前提;
- 安装引导放 PR-5;
- 只生成和展示操作计划;
- 用户在自己的管理员终端执行;
- 不把 setup 暴露给模型。
结论:安装自动化不得阻塞 PRODUCT-1A/B,也不得反向扩大范围。
问题成立。若把 SSH 抽象、Store v2、向导、doctor、安装器一次提交,回归面过大。
修正:
- 固定五个 PR;
- PR-1 只做兼容 transport;
- PR-2 只做数据与 manifest;
- PR-3 才接用户入口;
- PR-4 做 doctor/UI;
- PR-5 独立处理安装和真实 E2E;
- 每步保留 legacy path。
结论:按切片执行后规模可控。
以下任一项不满足,PRODUCT-1 不得判定完成:
- Alias 连接仍被展开重建为 host/key 命令;
- Legacy explicit endpoint 未使用
-F none隔离 SSH config; - 在静态安全审计前执行
ssh -G,或支持/静默执行ProxyCommand、KnownHostsCommand、Match exec; - SSH stderr 原样进入日志;
- SSH 子进程继承模型/API Token 等无关环境;
- Agent 状态被当成目标认证成功;
- 新 host key 被自动接受;
- profile/hash 从远端首次响应自动学习;
- doctor 调用了业务工具或挂载 ToolRegistry;
/remote add自动执行 sudo;- legacy REMOTE-0 E2E 失效;
- 失败后仍有 SSH 进程、mount 或 active target;
- 把普通进程服务、写能力、多主机或自动修复写进本期完成范围。
PRODUCT-1 只有在以下证据同时存在时才能从 [ ] 改为 [x]:
- PR-1~PR-5 的代码路径全部进入真实 CLI;
- Unit/Component/Integration 全部通过;
- legacy REMOTE-0 E2E 通过;
- Windows/Linux 真实 OpenSSH E2E 通过;
- APP_DOWN 与 POSTGRES 两个只读 manifest 均通过 attestation;
- unknown/changed host key、Agent、ProxyJump、profile drift 负例通过;
- secret scan 和
git diff --check通过; - 从已有 alias 到首次 READY 的实际记录满足 3 分钟和 3 个主要操作;
- README、DESIGN、TODO 与本方案同步;
- 没有用测试数量替代用户体验验收。