Files
vps-commander/docs/DC_REFACTORING_AND_SECURITY_SPEC.md.pre-review-fix-20261002
2026-10-02 13:23:30 +08:00

492 lines
16 KiB
Plaintext
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.
# 《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)<br>• `read_multiple_files`<br>• `list_directory` (结构化目录树)<br>• `get_file_info` (元数据)<br>• `start_search` / `get_more_search_results`<br>• `list_processes` / `list_sessions`<br>• `read_process_output` (拉取输出) | **Low**<br>**Medium**<br>**High** |
| 🟡 **Medium(文件写)** | **文件与目录变更,但严禁拉起任意二进制或执行命令** | • `edit_block` (局部文本块精准替换,核心)<br>• `write_file` (文件新建/全量写入)<br>• `create_directory` (新建目录)<br>• `move_file` (移动/重命名) | **Medium**<br>**High**<br>*(Low 拦截)* |
| 🔴 **High(完全控制)** | **具备操作系统执行权、进程生命周期调度与管道干预** | • `exec_command` (同步短命令)<br>• `start_process` (后台异步启动长进程)<br>• `interact_with_process` (stdin 交互写入)<br>• `kill_process` / `force_terminate` (终止进程) | **High**<br>*(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 实现。**