docs: synchronize architecture deployment and MCP documentation
Release / Build & Release (push) Canceled after 0s
Release / Build & Release (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
# vps-commander 生产 Bug Hunt 报告(2026-10-02)
|
||||
|
||||
- **测试人**:安全测试员(独立 bug hunt)
|
||||
- **测试时间**:2026-10-02 13:36–13:50(+08)
|
||||
- **测试对象**:生产 Hub(wjyhk,13:36:06 重启)+ 6 节点 Agent
|
||||
- **测试方式**:只读探测 + 无害命令(echo/true),未写生产文件、未改配置、未重启服务、未碰 commander.db
|
||||
- **结论**:发现 **4 个 bug**,最高严重度 **高**。另有若干“未发现问题”的项如实记录。
|
||||
|
||||
---
|
||||
|
||||
## 【标题】生产 Agent 二进制构建自脏工作树,缺失已提交代码
|
||||
|
||||
**【严重度:高】**
|
||||
|
||||
**【复现步骤】**
|
||||
1. 在 wjyhk 执行 `go version -m /opt/vps-commander/vps-commander-agent`,观察 `vcs.revision=a5620cb` 且 `vcs.modified=true`。
|
||||
2. 对比源码:`git show a5620cb:cmd/agent/main.go` 的 action switch 只有 6 个 case(mcp_list/mcp_test/mcp_call/exec/file_read/file_write),无 session 相关、无 `read_multiple_files`。
|
||||
3. 但生产 Agent 实际能处理 `start_process`(session 功能可用),说明二进制内容无法从任何 commit 还原——是脏树构建。
|
||||
4. Hub 同样 `vcs.modified=true`(13:36 重部署的二进制,revision=556870b)。
|
||||
|
||||
**【证据】**
|
||||
```
|
||||
build vcs.revision=a5620cb9c4d3570cf5ce2abe8772ef4e3b91bd76
|
||||
build vcs.time=2026-10-01T16:45:10Z
|
||||
build vcs.modified=true
|
||||
```
|
||||
各节点 Agent md5 一致(35e965a9),均为同一脏构建,于 13:22 统一重启部署。
|
||||
|
||||
**【影响分析】**
|
||||
- 生产二进制不可复现:出问题时无法用 `git checkout <rev> && go build` 还原现场,排障与回滚失去基准。
|
||||
- 脏构建内容未知:`read_multiple_files` 在源码 192969d 中已加入 Agent,但生产 Agent 不支持(见 Bug #3),说明构建时的脏树处于部分完成状态,**可能还缺失其他已提交的安全修复**,需逐项核对。
|
||||
- **建议**:从干净 HEAD 重新构建 Hub+Agent,全量重新部署,部署后校验 `vcs.modified` 为空且 revision==HEAD。
|
||||
|
||||
---
|
||||
|
||||
## 【标题】`read_multiple_files` 在所有远端节点上失败(Hub-Agent 协议不一致)
|
||||
|
||||
**【严重度:中】**
|
||||
|
||||
**【复现步骤】**
|
||||
1. `vps-mcp.py call read_multiple_files '{"device":"hwsg","paths":["/etc/hostname"],"limit":100}'` → 返回 `{"error": "unsupported action"}`。
|
||||
2. 同样调用 `device=wjyhk`(本地)→ 正常返回文件内容。
|
||||
3. 查源码:`internal/api/mcp_adapter.go:287` 的 `ReadMultipleFiles` 对远端走 `Cluster.ReadMultipleFiles`;Agent 侧 `cmd/agent/main.go:261` 在当前源码中有该 case,但生产 Agent 二进制(脏构建)不支持,落入 `default: unsupported action`(main.go:391)。
|
||||
|
||||
**【证据】**
|
||||
```json
|
||||
{"error": "unsupported action"}
|
||||
```
|
||||
(hwsg/N3450 等远端节点复现,wjyhk 本地正常)
|
||||
|
||||
**【影响分析】**
|
||||
- MCP 工具列表向 AI 客户端 quảng cáo `read_multiple_files` 可用于所有设备,实际 5/6 节点不可用,功能性 bug。
|
||||
- 根因是 Bug #1(脏构建)的直接后果;修复 Bug #1(干净重建+重部署)后本项自动解决。
|
||||
- 在修复前,建议 Hub 对不支持该 action 的 Agent 返回明确错误而非透传 `unsupported action`,或在 capability 协商中隐藏该工具。
|
||||
|
||||
---
|
||||
|
||||
## 【标题】`/api/v1/process/session` 请求结构体无 JSON tag,snake_case 字段被静默丢弃
|
||||
|
||||
**【严重度:中】**
|
||||
|
||||
**【复现步骤】**
|
||||
1. `POST /api/v1/process/session?action=start`,body `{"device":"hwsg","command":"echo hi","session_id":"my-id"}`(snake_case,如响应格式所示)。
|
||||
2. 响应返回 `{"session_id":"04d39d85b150..."}`——**客户端传的 ID 被丢弃**,服务端生成了随机 ID。
|
||||
3. 随后用 `my-id` 做 read/stop → `session not found`。
|
||||
4. 改用精确大小写 `{"SessionID":"my-id"}` → 全流程正常。
|
||||
|
||||
**【证据】**
|
||||
```go
|
||||
// internal/api/api.go:784
|
||||
var req struct {
|
||||
Device, SessionID, Command, Workdir, Data string // 无 json tag!
|
||||
Force bool
|
||||
RingCapacity int `json:"ring_capacity"`
|
||||
}
|
||||
```
|
||||
Go encoding/json 对无 tag 字段仅做大小写不敏感匹配,`session_id`(snake_case)无法匹配 `SessionID`,被静默忽略。
|
||||
|
||||
**【影响分析】**
|
||||
- API 契约破裂:响应用 `session_id`(snake_case),请求却要求 `SessionID`(CamelCase);任何按常规 REST 惯例(或照着响应字段)写的客户端都会踩坑,且失败是静默的(拿到一个“成功”但对不上的 session)。
|
||||
- 非安全漏洞,但属于生产功能 bug。修复:在结构体上加 `json:"device"` 等 snake_case tag(注意与 openapi.json 文档对齐)。
|
||||
|
||||
---
|
||||
|
||||
## 【标题】`/api/v1/process/session` 不支持 Hub 本节点(无本地回退)
|
||||
|
||||
**【严重度:中低】**
|
||||
|
||||
**【复现步骤】**
|
||||
1. `POST /api/v1/process/session?action=start`,body `{"Device":"wjyhk","Command":"echo hi"}`。
|
||||
2. 返回 `device offline`(HTTP 409)。
|
||||
3. 对比:`POST /api/v1/exec` 同样 `device=wjyhk` 可正常执行(`exec` 有 `isRemote` 本地回退,api.go:744)。
|
||||
|
||||
**【证据】**
|
||||
```go
|
||||
// processSession 直接走 Cluster,无 isRemote 判断:
|
||||
if s.Cluster == nil { http.Error(w, "cluster unavailable", 503); return }
|
||||
...
|
||||
result, err = s.Cluster.StartProcess(ctx, req.Device, ...) // wjyhk 无 agent WS 连接 → device offline
|
||||
```
|
||||
|
||||
**【影响分析】**
|
||||
- 功能不一致:exec/read/write 等在 Hub 本节点可用,唯独 session 管理不可用。用户在 Hub 本机无法使用长会话功能。
|
||||
- 修复:给 processSession 加与 exec 相同的 `isRemote` 本地回退(Hub 侧需持有一个本地 SessionManager,或复用 executor 包)。
|
||||
|
||||
---
|
||||
|
||||
## 未发现问题的项(逐项如实记录)
|
||||
|
||||
1. **认证绕过**:无 token/错 token/空 Bearer 访问 `/api/v1/*` 与 `/panel/api/*` 均返回 401;`/healthz` 200 为预期设计。未发现绕过。
|
||||
2. **畸形输入**:malformed JSON、空 body、300KB 超大 body(超 256KB MaxBytesReader 限制)均返回 400,无 panic。Hub 日志自 13:24 以来 0 panic、0 error。
|
||||
3. **路径穿越**:`../../../etc/hostname` 可读、`\0` 被 `validPath` 拦截(400)。但本工具是已认证管理员工具,无沙箱设计约束,`..` 解析属操作系统正常行为,不视为漏洞。
|
||||
4. **设备名注入**:空字符串回退本机(设计如此)、不存在设备/超长字符串/含 `\0` 的设备名均 `security mode unknown denies exec`,fail-closed,无 panic。
|
||||
5. **Session 生命周期**:Agent 侧每分钟 `sessions.Cleanup()`,idle TTL 10min + hard TTL 存在;`SessionManager` 有互斥锁,5 并发创建 session 无竞态崩溃(2 成功、3 因达到上限 4 被正确拒绝,上限由 `MaxSessionBufferTotal/DefaultRingCapacity=4` 决定,符合设计)。
|
||||
6. **并发压力**:10 并行原生 HTTP 请求全部 200、响应快;Hub 内存稳定(~20MB)。30 并行 MCP 调用大量超时系客户端 SSE 建连开销,非 Hub 问题。
|
||||
7. **配额**:`ExecLimiter` 32 并发信号量存在;无单客户端速率限制(设计选择,非 bug)。
|
||||
8. **Agent-MCP 畸形参数**:不存在的 tool/含 `\0` 的参数返回干净的 `MCP HTTP status 400`,无 panic、无信息泄漏。
|
||||
9. **kill 进程**:API 只接受 SessionID,Agent 用内部 `cmd.Process` 句柄 kill,不接受客户端 PID,无 PID 重用误杀风险。
|
||||
|
||||
---
|
||||
|
||||
## 附:测试期间观察到的部署活动
|
||||
|
||||
- 13:36:06 Hub 被重部署(此前为 13:24),二进制 revision 由 38382d2 变为 556870b。测试期间生产处于活跃变更中,部分结论(如 Hub revision)以测试时刻为准。
|
||||
- Agent 在各节点为同一脏构建(md5 35e965a9,13:22 部署),`vcs.modified=true`。
|
||||
|
||||
---
|
||||
|
||||
*安全测试员,2026-10-02*
|
||||
Reference in New Issue
Block a user