# 《VPS-Commander 平滑升级改造与三级安全拦截实施方案》 ## 1. 改造目标与背景 本项目计划全量复刻 **Desktop Commander(DC)** 在终端与文件系统上的核心工程能力(包括局部块级精准替换、后台长进程生命周期管理、交互式 stdin 注入、结构化目录遍历与只读检索),并将其无缝接入 **Low(低风险)/ Medium(中风险)/ High(高风险)** 三级安全门禁体系。 **核心红线与验收标准:** 1. **服务不中断**:升级过程不影响正在运行的 Agent 探针与 Hub 中心。 2. **协议向后兼容**:旧版 Agent 与新版 Hub 混合共存,老接口绝不废弃。 3. **性能零损耗**:杜绝动态运行时映射,安全校验在编译期即已绑定。 4. **轻量化保真**:继续保持纯 Go 静态编译的高效特性,严控内存占用(常驻内存锁定在 20MB 以内)。 --- ## 2. 工具池全量复刻与三级风险定级表(静态强绑定) 所有功能根据系统破坏力与副作用大小,在**编译期**严格归属于三级风险体系: | 风险等级 (Level) | 行为边界定义 | 包含的复刻工具 (DC 标杆) | 允许放行的安全模式 | | :--- | :--- | :--- | :--- | | 🟢 **Low(只读/感知)** | **纯只读、无状态改变、无系统副作用** | • `read_file` (支持切片与 Tail)
• `read_multiple_files`
• `list_directory` (结构化目录树)
• `get_file_info` (元数据)
• `start_search` / `get_more_search_results`
• `list_processes` / `list_sessions`
• `read_process_output` (拉取输出) | **Low**
**Medium**
**High** | | 🟡 **Medium(文件写)** | **文件与目录变更,但严禁拉起任意二进制或执行命令** | • `edit_block` (局部文本块精准替换,核心)
• `write_file` (文件新建/全量写入)
• `create_directory` (新建目录)
• `move_file` (移动/重命名) | **Medium**
**High**
*(Low 拦截)* | | 🔴 **High(完全控制)** | **具备操作系统执行权、进程生命周期调度与管道干预** | • `exec_command` (同步短命令)
• `start_process` (后台异步启动长进程)
• `interact_with_process` (stdin 交互写入)
• `kill_process` / `force_terminate` (终止进程) | **High**
*(Low / Medium 拦截)* | --- ## 3. 架构改造与平滑过渡设计 ### 3.1 协议层平滑升级(零破坏向后兼容) 在 `internal/cluster/protocol.go` 中,保留原有字段,新增可选的扩展动作: ```go type Message struct { ID string `json:"id,omitempty"` Action string `json:"action,omitempty"` // 保持原有字段: "read" / "write" / "exec" / "probe" SubAction string `json:"sub_action,omitempty"` // ✨ 增量动作: "edit_block", "start_process" 等 Device string `json:"device,omitempty"` Payload any `json:"payload,omitempty"` ... } ``` * **兼容保障**:老版本 Agent 接收到带 `SubAction` 的 JSON 消息时会自动忽略多余字段,继续按老逻辑工作;升级后的 Agent 优先路由给 `SubAction` 处理。 ### 3.2 Hub 门禁与高并发性能优化(读写分离) 现有系统每次请求均去 SQLite 查询策略,高并发下存在文件锁瓶颈。改造后引入 **RWMutex 内存策略快照**: 1. **读路径(Fast Path,纳秒级)**: ```go // 常量数值定义,直接内联比较 const ( RiskLow = 1; RiskMedium = 2; RiskHigh = 3 ) func (s *Server) requireSecurity(w http.ResponseWriter, r *http.Request, device string, requiredRisk int, detail string) bool { allowed := s.getEffectiveRiskLevel(device) // 查内存 RLock,耗时 < 10ns if allowed >= requiredRisk { return true } // 记录 audit_logs ("security_denied", 403) 并返回拦截响应 return false } ``` 2. **写路径(Slow Path,低频)**: * 仅在管理员通过 Web 界面更改安全策略时,写一次 SQLite 并原子更新内存缓存。 3. **老 API 零改动**: * 原有 `/api/v1/devices/exec`、`/read`、`/write` 继续保留,老脚本老客户端零感知。 ### 3.3 Agent 端核心引擎实现(防资源膨胀设计) * **`edit_block` 引擎**: * 读取原文件后校验 `old_text` 全局唯一性,唯一时单次替换并使用临时文件进行原子覆写(`os.Rename`)。 * 相比原有全量上传,网络传输和内存开销下降 95%。 * **长进程与 PTY 会话引擎(`start_process` 等)**: * Agent 内部建立轻量 `SessionManager`,挂接内核标准输入输出管道。 * **硬编码内存锁(Circular RingBuffer)**:每个进程会话分配最大 2MB 环形日志缓冲区,达到上限自动覆盖最早输出,**从物理上杜绝日志死循环导致的内存溢出(OOM)**。 --- ## 4. 资源开销评估与性能上限控制 得益于纯 Go 原生静态编译,系统坚决不引入任何 Node.js/Python 运行时,资源开销保持极致克制: | 评估指标 | 改造前基准(实测) | 改造后预期 | 性能/资源防护机制 | | :--- | :--- | :--- | :--- | | **Agent 静默内存** | ~13 MB | **~15 MB - 18 MB** | 零后台轮询,事件驱动 | | **Agent 峰值内存** | 随命令大小波动 | **基准 + (2MB × 活跃会话数)** | 环形缓冲区硬顶锁定,防内存泄露 | | **Hub 服务端内存** | ~16 MB | **~18 MB - 22 MB** | 内存门禁仅占不到 10KB | | **二进制体积** | ~9.4 MB | **~11 MB - 13 MB** | 静态单文件编译,极易分发部署 | | **安全判定延迟** | 毫秒级(查 SQLite) | **纳秒级(内存读锁)** | 单机可轻松支撑 10W+ QPS 门禁并发 | --- ## 5. 四阶段实施与回滚路线图(稳健落地) * **Phase 1:服务端门禁升级与内存读缓存** * 在 Hub 端落地 `RWMutex` 安全策略缓存; * API 入口统一接入 `requireSecurity(..., RiskLevel)`; * *验证标准*:旧版 4 项基础功能与 Web 面板完好,并发查询 QPS 大幅提升。 * **Phase 2:落地中风险神器 `edit_block`** * Agent 端实现块级精准替换算法; * Hub 暴露 `/api/v1/devices/file/edit_block` 并注册至 MCP Adapter; * *验证标准*:在 Medium 模式下可正常修配置文件,在 Low 模式下被精确阻断并记入审计。 * **Phase 3:落地高风险长进程引擎** * Agent 端引入带 RingBuffer 的 `SessionManager`; * 实现 `start_process`、`read_process_output`、`interact_with_process`、`kill_process`; * *验证标准*:跑耗时任务不会超时断连,支持实时 Tail 查看输出与注入 stdin。 * **Phase 4:落地只读结构化文件与系统感知(Low)** * 实现 `list_directory`、`get_file_info` 及后台异步只读搜索。 --- ## 6. 评估与审计重点建议 1. **安全维度**:Low/Medium/High 对能力的切分是否彻底杜绝了提权与逃逸风险? 2. **性能维度**:使用内存 RWMutex + 编译期静态安全级别判定,是否彻底解决了单文件 SQLite 高频读瓶颈? 3. **稳定性维度**:为每个长进程会话限定 RingBuffer 内存上限,是否有效防止了边缘 VPS 探针被日志撑爆? --- # 7. 架构收敛修订(2026-10-02) > 本章节在进入代码实施前增加,用于两位外部 Agent 审核。原方案的工具分级、四阶段路线和向后兼容原则继续保留;本章节对安全权威、策略一致性、工具注册以及高风险 Session 生命周期作进一步约束。 ## 7.1 安全等级的正确边界:静态工具风险 + 运行时设备策略 原方案中“静态强绑定”应准确解释为:**工具所需风险等级在编译期固定,是否允许执行在运行时由 Hub 的权威策略决定。** 不得将设备当前 Low/Medium/High 策略编译进 Agent,也不得允许 Agent 自行提升自身安全等级。 ### 7.1.1 工具风险等级 每个工具在代码注册表中拥有不可运行时修改的 `RequiredRisk`: ```go const ( RiskLow = 1 RiskMedium = 2 RiskHigh = 3 ) type ToolSpec struct { Name string RequiredRisk int ReadOnly bool } ``` 示例: ```text read_file / list_directory / search -> Low edit_block / write_file / move_file -> Medium exec_command / start_process / stdin -> High ``` 禁止通过 Web 面板、Agent 配置文件或远端请求动态修改工具自身的 `RequiredRisk`。 ### 7.1.2 设备有效策略 设备的 `EffectiveRisk` 是 Hub 运行时策略,不属于 Agent 自主能力。 请求授权模型: ```text Request -> identify device -> identify ToolSpec.RequiredRisk -> read Hub PolicySnapshot -> compare EffectiveRisk >= RequiredRisk -> allow / deny ``` 因此完整安全边界为: **编译期固定“工具需要什么权限” + 运行时决定“该设备当前获得什么权限”。** --- ## 7.2 Policy Store 与 Policy Snapshot 一致性模型 SQLite 继续作为持久化权威源(durable source of truth),但请求热路径禁止每次查询 SQLite。 ### 7.2.1 数据流 ```text Admin Web | v SQLite transaction | v Build new immutable PolicySnapshot | v Atomic publish / swap | v Request fast path ``` 请求只读取当前不可变 snapshot;不在请求过程中修改策略对象。 ### 7.2.2 更新原则 策略修改必须满足: 1. SQLite 写入成功后才允许发布新 snapshot。 2. SQLite 写失败时,内存策略保持旧版本。 3. snapshot 发布必须是原子操作,不允许出现半更新状态。 4. Hub 启动时必须从 SQLite 完整重建 snapshot。 5. snapshot 应携带单调递增 `Version`,便于审计和诊断。 6. 审计记录应包含 `device`, `oldRisk`, `newRisk`, `version`, `actor`, `timestamp`。 推荐结构: ```go type PolicySnapshot struct { Version uint64 Devices map[string]DevicePolicy CreatedAt time.Time } ``` 热路径原则: ```text load current snapshot -> lookup device -> compare integer risk -> allow/deny ``` 不得在高频 API 请求中重新读取 SQLite 策略表。 ### 7.2.3 故障安全 如果 Hub 无法加载有效策略 snapshot: - 不得默认放行 High。 - 不得从请求参数接受临时风险等级。 - 对需要 Medium/High 的操作默认拒绝。 - Low 只读能力是否可继续开放,必须由启动时的明确安全默认策略决定,并记录审计事件。 --- ## 7.3 Tool Registry:统一能力与风险注册表 避免各 API handler 自己实现不同的安全判断。所有能力必须进入统一 Tool Registry。 建议: ```go type ToolSpec struct { Name string RequiredRisk int Category string ReadOnly bool Description string } ``` 例如: ```text read_file Low read_multiple_files Low list_directory Low get_file_info Low start_search Low get_more_search_results Low list_processes Low list_sessions Low read_process_output Low edit_block Medium write_file Medium create_directory Medium move_file Medium exec_command High start_process High interact_with_process High kill_process High force_terminate High ``` API、MCP Adapter 和内部调用均通过 Registry 获取 `RequiredRisk`,不得在多个入口复制风险数字。 --- ## 7.4 Agent 信任边界 Agent 是执行者,不是安全策略的最终裁决者。 Hub 是设备授权策略的权威来源。 但 Agent 仍必须执行本地最低限度安全校验,包括: - 请求格式与参数校验; - session 所有权校验; - session 是否仍然有效; - 单次输入/输出大小限制; - 超时和资源限制; - 不接受客户端自行声明的 `RiskLevel` 作为授权依据。 Agent 不得因为请求中携带: ```json {"risk":"high"} ``` 或类似字段而自行获得 High 权限。 如果协议未来需要携带授权信息,应使用 Hub 生成的受保护授权上下文,并绑定: ```text device + request/session + policy version + expiry ``` 具体签名/令牌方案在实现阶段单独评审。 --- ## 7.5 High Risk SessionManager 安全模型 `start_process`、`interact_with_process`、`kill_process` 不应被视为普通 HTTP 命令,而应视为一个受生命周期管理的高风险 Session。 ### 7.5.1 Session 属性 每个 Session 至少包含: ```go type ProcessSession struct { ID string Owner string Device string PID int CreatedAt time.Time LastAccess time.Time ExpiresAt time.Time State string OutputBytes uint64 } ``` Session ID 必须不可预测,并且不能复用操作系统 PID 作为唯一身份。 ### 7.5.2 所有权 只有创建 Session 的授权调用方才能: - 读取该 Session 输出; - 向 stdin 写入数据; - 终止该 Session。 管理员级操作如果未来需要跨 Owner 接管 Session,应定义明确的审计事件和单独权限,不得通过普通 session API 隐式获得。 ### 7.5.3 生命周期 ```text start_process | v RUNNING / \ / \ EXITED EXPIRED | | +----+---+ v CLEANUP ``` Agent 重启后所有旧 Session 必须失效,不得从持久化文件自动恢复可写 stdin 的旧 Session。 ### 7.5.4 时间限制 Session 必须具备: - 最大生命周期; - 空闲超时; - stdin 单次大小限制; - stdin 速率限制; - 输出缓冲区上限。 具体默认数值在实现前通过压测确定,不在设计阶段硬编码未经验证的数值。 --- ## 7.6 RingBuffer 与全局资源上限 原方案中的“2MB × 活跃 Session”只能作为单 Session 上限,不应视为 Agent 的总内存预算。 必须同时存在: ```text per-session output cap + max active sessions + agent-wide session buffer cap + process creation rate limit ``` 例如: ```text 单 Session:最大 2MB output buffer 全局:最大 N 个活跃 Session 全局:最大 M MB Session buffer ``` 当达到任意全局上限时,新 Session 必须被拒绝并记录审计,而不是继续消耗内存。 RingBuffer 的覆盖行为只能覆盖旧输出,不得覆盖 session 元数据、错误状态或安全审计记录。 --- ## 7.7 审计要求 至少记录以下安全事件: ```text security_denied policy_changed session_created session_expired session_killed session_owner_denied resource_limit_denied ``` High Risk 操作的审计信息必须包含: ```text device actor / owner request id session id(如适用) tool result policy version timestamp ``` 审计日志不能因为 Session RingBuffer 达到上限而被覆盖。 --- ## 7.8 协议兼容补充约束 旧 Agent 与新 Hub 混合运行期间: 1. 原有 `action=read/write/exec/probe` 字段继续有效。 2. 新 `SubAction` 为可选字段。 3. 新字段不能改变旧 Agent 对旧消息的既有语义。 4. Hub 必须根据 Agent capability/version 判断是否可以下发新 SubAction。 5. 对不支持新能力的 Agent,返回明确的 capability error,而不是让旧 Agent 猜测。 6. 安全门禁必须在能力路由之前执行,避免通过旧 API 绕过新 Tool Registry。 --- ## 7.9 实施前必须完成的设计验收 在进入 Phase 1 代码改造前,两位审核 Agent 应重点审查: ### A. 权威性 - Hub 是否始终是 EffectiveRisk 的唯一权威来源? - Agent 是否存在任何自升权路径? - 旧 API 是否可能绕过 Tool Registry? ### B. 一致性 - SQLite commit 与 snapshot publish 顺序是否正确? - Hub 重启能否确定性恢复策略? - snapshot version 是否足以定位策略变更? ### C. Session 安全 - session ownership 是否不可伪造? - Agent 重启后旧 session 是否必然失效? - stdin、stdout、session 数量是否存在全局资源上限? ### D. 向后兼容 - 老 Agent + 新 Hub 是否继续工作? - 新 Hub 是否能识别 Agent capability? - 新能力是否可能通过旧接口绕过门禁? ### E. 性能 - 热路径是否完全脱离 SQLite? - snapshot 是否真正 immutable / atomic? - session ring buffer 是否存在隐藏的额外复制? **只有上述问题通过审核后,才进入 Phase 1 实现。**