Files
vps-commander/docs/BUGHUNT_20261002.md
2026-10-02 13:53:00 +08:00

130 lines
7.6 KiB
Markdown
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 生产 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*