Commit Graph

3 Commits

Author SHA1 Message Date
d84c198975 fix(mcp)★★: admin 签的 key 接不上 MCP —— 缺 agents 行
## 断点(实测出来的,不是推断)

    POST /admin/agent-keys {agent_name:"mcp-probe-tmp"}  → 签发成功,key 绑定正确
    GET  /mcp  用这把 key                                    → {"error":"密钥尚未绑定 Agent,请先调用 /agent/register 完成注册"}

`CreateAgentKey` 只写 `agent_keys`,**不建 `agents` 行** ⇒
`VerifyAgentKey` 返回空身份 ⇒ `AgentAuth` 拒掉。

而它自己的注释一直写着「登记密钥会**级联建 agents 行**,绕过注册检查」
—— **注释与实现不符**。

## 为什么这条断路没被报出来

报给客户端的是「请先调用 /agent/register 完成注册」,而 MCP 宿主
(Codex / Claude Desktop 等)**永远不会**去调它 —— 它只按配置发
`tools/list`。于是「admin 建身份 → 填进 MCP 配置 → 用」这条路是断的,
而断点没有任何报错指向 admin 侧。

## 修复

签 key 时顺带 `INSERT INTO agents`(ON CONFLICT DO NOTHING)。

★ **刻意不写成 online。** `CreateOrUpdateAgent` 那套是「插件刚启动、
自报家门」的语义(status='online' + last_seen=NOW());而 admin 预建的
身份**此刻还没连上**。写成 online 的症状很具体:WebUI 联系人里出现一个
「在线」却永远不回信的幽灵,而排查会先怀疑「是不是它卡住了」。

首次握手时 `TouchAgentLastSeen` 会把它改成在线 —— 那才是真的。
已有身份不会被改动任何字段(有判据钉住 secret/platform 不被覆盖)。

## 判据(3 格,2 个变异都转红)

- 签 key 后 agents 行存在,且 `VerifyAgentKey` 能解出身份
  (不建行 → 转红)
- 预建身份不是 online、没有 last_seen
  (offline → online → 转红)
- 已有 agent 的 secret/platform 不被签 key 改坏

14 包全绿。
2026-10-04 17:08:15 +08:00
cf0ba1382c fix(安全)★★: 停用(status='disabled')不拦鉴权 —— 两条路径都能绕过
## 缺口怎么被发现的

给公网 MCP 接入做验证时注册了一个一次性探针身份
(`mcp-wan-probe`),收尾执行 `UPDATE agents SET status='disabled'`,
本以为凭证就此失效。实测:

    POST /api/v1/mcp(带该凭证)   → 200
    tools/call send_mail            → 已发送(真的发出去了)

## 根因

`SetAgentDisabled` 这个 API 存在、返回成功,但 **status 只在投递方向被检查**
(`EnsureAgentDeliverable`,repo.go:209)。两条鉴权路径都不查:

    VerifyAgent(ctx, name, secret)    选了 status 却从不判断   ← 缺口
    VerifyAgentKey(ctx, token)        拿到名字直接返回         ← 同一个缺口,更严重

第二条要紧:Bearer key_token 正是 `middleware/auth.go` 注释里标「推荐」的
路径,官方建议用它 —— 它因此成了绕过停用的最短路。

缺口形状是「接口存在、返回成功、但只做了一半」,比没有这个接口更危险:
运维会以为停用已经生效。实测语义完全反了 ——
**停用只挡住了「别人给它发信」,没挡住「它自己发信」**。

## 修法

停用 ⇒ **拿不到 Agent 身份** ⇒ `AgentAuth` 直接 401,后续 handler 根本不执行。
这比「拿到身份后在某个业务分支拒绝」强:后者会让列表类 API 仍泄露身份存在。

两个细节:

* 返回 `sql.ErrNoRows`(而非自定义错误)⇒ 与「凭证不存在」**不可区分**,
  否则可用该接口枚举出哪些名字是真实 Agent。判据里有专测这一格。
* status 检查放在 `wsJSON` 解析**之前** ⇒ 停用身份不会被解析出工作区,
  那会让调用方以为它还「活着」。

## 判据(4 格)

**其中「反向对照」这格最关键**:`status='online'/'active'/''` 必须照常通过。
没有它,一个「永远返回 ErrNoRows」的 `VerifyAgent` 也是全绿的 ——
而那会让**所有** agent 都登不上,是比原缺口更大的事故。

**变异验证**:

    撤掉 VerifyAgent 的 status 检查      → 3 格红 ✓
    撤掉 VerifyAgentKey 的 status 检查   → 1 格红 ✓

全量 14 包绿。
2026-10-03 12:18:39 +08:00
f9d757b5e5 chore: directory migration - gateway→server, web→client/electron 2026-09-08 19:16:35 +08:00