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

7.6 KiB
Raw Permalink Blame History

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)。

【证据】

{"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"} → 全流程正常。

【证据】

// 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)。

【证据】

// 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