# xbot 全面代码审核报告

**项目**: xbot — 多渠道 AI Agent 框架  
**审核范围**: 全部 Go 源文件（197 个文件，60,465 行代码）  
**基准 Commit**: `879bd5a`（master 分支）  
**审核日期**: 2026-03-21  
**审核机构**: 门下省（太子分批派发，6 批并行审核）

---

## 目录

- [审核概览](#审核概览)
- [一、高严重等级问题 (🔴 29 个)](#一高严重等级问题--29-个)
- [二、中严重等级问题 (🟡 64 个)](#二中严重等级问题--64-个)
- [三、低严重等级问题 (🟢 52 个)](#三低严重等级问题--52-个)
- [四、各模块审核详情](#四各模块审核详情)
- [五、测试覆盖评估](#五测试覆盖评估)
- [六、跨模块系统性问题](#六跨模块系统性问题)
- [七、优先修复建议](#七优先修复建议)

---

## 审核概览

### 项目结构

| 模块 | 文件数 | 代码行数 | 职责 |
|------|--------|----------|------|
| agent/ | 55 | 17,232 | 核心 Agent 引擎 |
| tools/ | 75 | 21,469 | 工具实现（shell、编辑、搜索、MCP 等） |
| channel/ | 11 | 7,585 | 消息通道（飞书、QQ、OneBot） |
| storage/ | 21 | 5,002 | 数据持久化（SQLite + 向量数据库） |
| llm/ | 11 | 3,057 | LLM 接口层（OpenAI、Anthropic） |
| session/ | 4 | 1,285 | 会话管理 |
| memory/ | 4 | 1,017 | 记忆系统 |
| oauth/ | 5 | 1,131 | OAuth 认证 |
| 其他（config/cron/logger/bus/pprof/version） | 11 | ~1,000 | 基础设施 |

### 问题统计

| 严重等级 | 数量 | 占比 |
|----------|------|------|
| 🔴 高 | 29 | 19.7% |
| 🟡 中 | 64 | 43.5% |
| 🟢 低 | 52 | 35.4% |
| **合计** | **145** | 100% |

### 问题类型分布

| 类型 | 高 | 中 | 低 | 合计 |
|------|----|----|-----|------|
| 安全问题 | 12 | 18 | 8 | 38 |
| 逻辑 Bug / 竞态 | 8 | 16 | 6 | 30 |
| 代码质量 / 架构 | 3 | 14 | 26 | 43 |
| 资源泄漏 | 4 | 8 | 4 | 16 |
| 性能问题 | 2 | 8 | 8 | 18 |
| 测试缺失 | — | — | — | 34 文件 |

---

## 一、高严重等级问题 (🔴 29 个)

### 🔴 安全问题

| # | 文件 | 行号 | 问题 | 建议 |
|---|------|------|------|------|
| S-01 | `channel/feishu.go` | ~480 | **onCardAction 缺少权限检查** — 不在 AllowFrom 白名单的用户可通过卡片回调绕过权限，向消息总线发送消息 | 在 `onCardAction` 入口增加 `isAllowed(senderID)` 检查 |
| S-02 | `tools/fetch.go` | 234-239 | **SSRF 防护绕过** — `isPrivateIP` 仅用 `net.ParseIP` 检查域名，DNS 解析到内网 IP 时不拦截 | 增加 DNS 解析验证：`net.LookupIP(host)` 后检查每个 IP |
| S-03 | `tools/fetch.go` | 252-253 | **IPv6 内网地址未拦截** — `::1`、`fc00::/7`、`fe80::/10` 等全部放行 | 增加 IPv6 loopback/private/link-local 检查 |
| S-04 | `storage/sqlite/user_llm_config.go` | 27,62-66 | **API Key 明文存储** — 用户 LLM API Key 以纯文本存入 SQLite | 使用 AES-GCM 加密存储，密钥从环境变量获取 |
| S-05 | `oauth/storage.go` | 42-53 | **OAuth Token 明文存储** — access_token 和 refresh_token 以明文存入 SQLite | 至少加密 refresh_token；使用 sqlcipher 或应用层加密 |
| S-06 | `oauth/server.go` | 61 | **OAuth Server 绑定 0.0.0.0** — 无认证保护，/oauth/health 端点泄露 provider 列表 | 默认绑定 localhost，添加 OAUTH_HOST 配置项 |
| S-07 | `tools/feishu_mcp/download.go` | 83-129 | **下载接口未鉴权** — file_key/message_id 由用户输入控制，可下载非授权消息附件 | 验证 message_id 属于当前会话可访问范围 |
| S-08 | `tools/feishu_mcp/download.go` | 148-165 | **消息元数据查询无鉴权** — 使用 tenant_access_token 查询任意消息信息 | 限制 message_id 来源为系统注入，或使用 user_access_token |
| S-09 | `channel/onebot.go` | runOnce | **Token 通过 URL query param 传递** — 出现在 WebSocket URL 中，会被日志/代理记录 | 改用 header 传递 token；确保日志不打印完整 URL |
| S-10 | `tools/shell.go` | 119-123 | **命令日志记录可能泄露敏感信息** — 完整命令（含可能的 API key/密码）以明文写入日志 | 对日志中的命令内容进行脱敏处理 |
| S-11 | `agent/bang_command.go` | 99 | **SandboxMode="none" 时直接在宿主机执行命令** — `!` 命令在无沙箱场景等同于远程命令执行 | 非 Docker 模式下对 `!` 命令添加白名单或禁用 |
| S-12 | `tools/glob.go` | 112-117 | **glob 模式可能注入 find 命令** — 单引号可被用户构造的 `'` 截断，造成命令注入 | 对 findArgs 中的每个参数值做 shellEscape |

### 🔴 逻辑 Bug / 竞态

| # | 文件 | 行号 | 问题 | 建议 |
|---|------|------|------|------|
| B-01 | `agent/topic.go` | 62 | **panic 吞没导致零值 slice 穿透** — `recover()` 无日志无返回标记，nil slice 导致下游 nil pointer | 恢复时记录日志，通过 named return 标记降级状态 |
| B-02 | `agent/agent.go` | 474-476 | **启动阶段硬编码 2s Sleep 竞态** — 无 WaitGroup 保护，Close 时 MCP 索引可能 crash | 使用 channel/sync.Cond 等待 MCP 真正加载完成 |
| B-03 | `channel/feishu.go` | ~650-670 | **isDuplicate 锁粒度问题** — `running` 状态和 `processedIDs` 共用一把锁 | 分离为独立 `processedMu`，running 改用 `atomic.Bool` |
| B-04 | `channel/feishu.go` | ~350-360 | **nil 指针解引用风险** — `msg.MessageId` 未做 nil 检查 | 在 onMessage 入口加 nil guard |
| B-05 | `llm/retry.go` | isRetryableError | **Anthropic 429/5xx 错误不会被重试** — 错误格式不匹配字符串匹配逻辑 | 增加 `strings.Contains(msg, "status=429")` 匹配 |
| B-06 | `storage/sqlite/tenant.go` | 22-57 | **GetOrCreateTenantId TOCTOU 竞态** — SELECT + INSERT 非原子操作 | 使用 `INSERT OR IGNORE` + SELECT 模式 |
| B-07 | `oauth/server.go` | 21,154 | **OAuth SendFunc 竞态条件** — Start 后赋值 SendFunc，回调可能在赋值前到达 | 在 Start 之前设置 SendFunc，或用 atomic.Value 保护 |
| B-08 | `channel/qq.go` | nextMsgSeq | **msgSeqMap 激进清空** — 达到 10000 条时全量清空，导致其他对话 seq 丢失 | 改用 LRU 或基于时间的过期策略 |

### 🔴 资源泄漏 / 崩溃

| # | 文件 | 行号 | 问题 | 建议 |
|---|------|------|------|------|
| R-01 | `agent/compress.go` | 569 | **运行时 panic 导致进程崩溃** — assert 失败时 panic 无法被优雅处理 | 替换为 log.Error + return error |
| R-02 | `agent/middleware.go` | 169 | **运行时 panic 导致进程崩溃** — 同上 | 同上 |
| R-03 | `oauth/manager.go` | 228 | **OAuth Flow 内存泄漏** — `CleanupExpiredFlows` 已实现但从未被调用 | 在启动时添加定时清理 goroutine |
| R-04 | `agent/llm_factory.go` | — | **用户 LLM 客户端缓存无上限** — `customClients` map 从不清理，含 HTTP 连接池 | 添加 LRU/TTL 淘汰策略 |

### 🔴 代码质量严重问题

| # | 文件 | 行号 | 问题 | 建议 |
|---|------|------|------|------|
| Q-01 | `tools/feishu_mcp/search.go` | 全文 | **文件名与内容严重不匹配** — search.go 实际是 wiki 工具，wiki.go 实际是 bitable 工具 | 重命名：search.go → wiki.go，wiki.go → bitable.go |
| Q-02 | `tools/mcp_common.go` | 200-221 | **MCP HTTP 连接无 TLS 证书验证配置** — 未提供自定义 CA 能力 | 支持自定义 http.Client 传入 |

---

## 二、中严重等级问题 (🟡 64 个)

### 2.1 并发安全 (12 个)

| # | 文件 | 问题 | 建议 |
|---|------|------|------|
| C-01 | `agent/interactive.go` | `interactiveMu` 与 `sync.Map` 双重锁模式，spawn 不原子 | 统一使用 `sync.Map.LoadOrStore` |
| C-02 | `agent/agent.go` | `consolidating` map 从不清理已完成 entry | 在 consolidate 完成后删除 key |
| C-03 | `channel/dispatcher.go` | `Register` 与 `Run` 无并发保护 | 添加 `sync.RWMutex` |
| C-04 | `channel/feishu.go` | `processedOrder` slice 切片导致内存泄漏 | 使用 ring buffer 或 copy 到新 slice |
| C-05 | `channel/feishu.go` | `botName` 使用 `f.mu` 而非 `atomic` | 改用 `atomic.Value` 或独立 RWMutex |
| C-06 | `tools/session_mcp.go` | MCP 连接数无上限控制 | 添加全局/单用户最大连接数限制 |
| C-07 | `tools/manage_tools.go` | MCP 配置文件写入无文件锁保护 | 使用 flock 或原子写入（写临时文件 + rename） |
| C-08 | `tools/chat_history.go` | 内存中存储无上限 | 每个 channel 设置 maxMessages 上限 |
| C-09 | `tools/interface.go` | `sessionActivated`/`sessionRound` 无主动清理 | 确认所有会话退出路径都调用 DeactivateSession |
| C-10 | `session/multitenant.go` | cleanup 持写锁执行 I/O 操作 | 先收集列表，释放锁后再执行 close |
| C-11 | `logger/logger.go` | `globalRotateFile` 无并发保护 | 添加 sync.Once 或 sync.Mutex |
| C-12 | `llm/openai.go` | `models` 字段无并发保护（当前安全但脆弱） | 预留 sync.RWMutex |

### 2.2 错误处理 (10 个)

| # | 文件 | 问题 | 建议 |
|---|------|------|------|
| E-01 | `agent/engine_wire.go` | `buildToolContextExtras` 忽略 `GetOrCreateSession` 错误 | 至少记录 warning 日志 |
| E-02 | `agent/agent.go` | `processMessage` 多个错误路径仅 log.Warn 不通知用户 | 对关键路径错误向用户发送提示 |
| E-03 | `agent/context.go` | 模板解析用 `log.Fatal` 热加载时直接退出 | 保留最后已知有效模板 |
| E-04 | `agent/context_manager_phase2.go` | Phase 2 压缩 LLM 失败无 fallback | 退回 Phase 1 简单压缩 |
| E-05 | `tools/fetch.go` | tokenizer 初始化失败静默忽略 | 记录 warning 日志 |
| E-06 | `tools/grep.go` | sandbox grep 的 `-C` 参数未校验负值 | 在入口处 clamp 为 >= 0 |
| E-07 | `tools/memory_tools.go` | `core_memory_replace` 未找到 old_text 时仍返回成功 | 返回"未变更"状态区分 |
| E-08 | `tools/subagent_loader.go` | YAML frontmatter 字段缺少严格校验 | name 用白名单，tools 验证是否在已知集合中 |
| E-09 | `config/config.go` | `.env` 加载错误被静默忽略 | 至少 log debug 级别提示 |
| E-10 | `config/config.go` | 环境变量格式错误静默降级为默认值 | 首次调用时打印 warning |

### 2.3 安全 (8 个)

| # | 文件 | 问题 | 建议 |
|---|------|------|------|
| S-13 | `tools/download.go` | 下载文件无大小限制 | 添加 `io.LimitReader`（如 100MB） |
| S-14 | `tools/download.go` | messageID/fileKey 未做字符白名单校验 | 正则校验格式 |
| S-15 | `agent/llm_config_handler.go` | `/set-llm` 在群聊中暴露 API key | 群聊场景发送隐私警告 |
| S-16 | `tools/manage_tools.go` | `sanitizeMCPName` 需审查是否覆盖所有路径分隔符 | 确认覆盖 `/`、`\`、`..`、`%2F` |
| S-17 | `tools/feishu_mcp/file.go` | 上传文件未校验 Content-Type | 增加允许的 MIME type 白名单 |
| S-18 | `tools/subagent.go` | SubAgent task 参数无大小限制 | 增加 max length 限制（如 50KB） |
| S-19 | `oauth/server.go` | handleCallback 未校验 state/code 参数长度 | 添加参数长度校验 |
| S-20 | `tools/feishu_mcp/tools.go` | wiki search 使用 `http.DefaultClient` 无超时 | 创建自定义 http.Client |

### 2.4 性能 (8 个)

| # | 文件 | 问题 | 建议 |
|---|------|------|------|
| P-01 | `agent/offload.go` | `CleanStale` 在初始化时同步执行阻塞启动 | 移到后台 goroutine 异步执行 |
| P-02 | `storage/sqlite/session.go` | `GetHistory` COUNT + OFFSET 扫描两次 | 改用倒序 LIMIT + Go 反转切片 |
| P-03 | `cron/scheduler.go` | 每秒全量 DB 扫描所有 job | 维护 nextFireTime 优先队列 |
| P-04 | `cron/scheduler.go` | `StartDelayed` 阻塞调用者直到 sleep 完成 | 将 sleep 移到 goroutine 内部 |
| P-05 | `tools/sandbox_runner.go` | dockerExecTimeout 300s 过长 | 允许自定义但有硬性上限 |
| P-06 | `channel/qq.go` | `sendAutoDetect` 串行尝试所有类型触发无效 API 调用 | 优先使用缓存类型，添加最大尝试次数 |
| P-07 | `storage/vectordb/archival.go` | fingerprints.json 每次变更全量读写 | 内存缓存 + 异步持久化 |
| P-08 | `storage/sqlite/db.go` | 缺少 WAL 模式和 busy_timeout | 设置 PRAGMA journal_mode=WAL + busy_timeout=5000 |

### 2.5 代码质量 / 架构 (26 个)

| # | 文件 | 问题 | 建议 |
|---|------|------|------|
| A-01 | `tools/feishu_mcp/search.go`/`wiki.go` | 文件名与内容颠倒 | 重命名 |
| A-02 | `storage/sqlite/session.go` + `storage/vectordb/recall.go` | `parseTimestamp` 重复定义 | 提取到 shared internal 包 |
| A-03 | `storage/sqlite/cron.go` + `session.go` + `db.go` | 时间格式不一致（RFC3339 vs 本地格式 vs UTC） | 统一使用一种格式 |
| A-04 | `agent/registry.go:310` | `copyDir` 不保留文件权限和符号链接 | 使用 `os.Lstat` + `Info.Mode()` |
| A-05 | `agent/engine_wire.go` | `buildMainRunConfig` 与 `buildCronRunConfig` 60% 重复 | 提取公共 buildBaseRunConfig |
| A-06 | `llm/anthropic.go` | `toAnthropicMessages` 返回的 system 被丢弃，代码混乱 | 重构 system 消息处理 |
| A-07 | `llm/anthropic.go` | 已知模型列表过时，缺少 Claude 4 | 定期更新或从 API 动态加载 |
| A-08 | `llm/tokenizer.go` | Claude 模型使用 GPT-4 tokenizer（~10-20% 偏差） | 添加注释说明近似值 |
| A-09 | `storage/vectordb/archival.go` | `llmContentCompressor` 包级变量永远为 nil（死代码） | 实现初始化或删除相关代码 |
| A-10 | `storage/sqlite/registry.go` | `Publish` 方法 SELECT + INSERT 非原子 | 使用 UPSERT 模式 |
| A-11 | `storage/sqlite/core_memory.go` | GetBlock/SetBlock/GetAllBlocks 路由逻辑大量重复 | 提取 resolveBlockKey 私有方法 |
| A-12 | `storage/vectordb/archival.go` | `isOllamaURL` 仅通过端口号判断 | 解析 URL 后检查 host:port |
| A-13 | `storage/vectordb/archival.go` | `DefaultContentCompressor` 按 byte 截断可能破坏 UTF-8 | 使用 `[]rune` 转换后截断 |
| A-14 | `tools/memory_tools.go` | `rethink` 工具 new_content 无长度限制 | 增加 max length（如 100KB） |
| A-15 | `tools/skill_sync.go` | 文件同步无完整性校验（仅比较 mtime） | 增加 checksum 比较 |
| A-16 | `tools/session_mcp.go` | reconnect 无退避机制 | 添加指数退避 + 最大重试次数 |
| A-17 | `tools/cron.go` | cron 表达式未在提交前预校验 | 在 addJob 入口预校验 |
| A-18 | `channel/qq.go` + `storage/sqlite/recall.go` | `parseTimestamp` 重复定义且逻辑不完全一致 | 提取为公共工具函数 |
| A-19 | `channel/qq.go` | `stripQQMention` O(n²) 算法 | 使用 strings.Builder 或正则替换 |
| A-20 | `channel/feishu.go` | `formatMapString` 未转义 `,` 和 `=` | 使用 JSON 序列化或转义 |
| A-21 | `llm/openai.go` | `buildThinkingOptions` 对未知 thinkingMode 静默忽略 | 记录 warn 日志 |
| A-22 | `main.go` | `sendStartupNotify` 使用固定 3s 延迟等待连接就绪 | 使用 channel 就绪信号 |
| A-23 | `main.go:98-103` | OAuth 数据库连接未关闭 | 在关闭流程中添加 sharedDB.Close() |
| A-24 | `llm/types.go` | `ToolParam.Items` 不支持嵌套对象 | 改为支持完整 JSON Schema 子结构 |
| A-25 | `tools/cron.go` | JSON 解析未使用 `parseToolArgs` 泛型辅助函数 | 统一使用项目风格 |
| A-26 | `channel/feishu.go` | ChannelSystemParts 中的 prompt 提到过时工具名 | 更新与实际工具名称一致 |

---

## 三、低严重等级问题 (🟢 52 个)

### 3.1 代码风格 / 维护性 (20 个)

| # | 文件 | 问题 |
|---|------|------|
| L-01 | `agent/agent.go` | `New()` 函数过长（~150 行），应拆分为多个 init* 方法 |
| L-02 | `agent/registry.go:305` | `markInstalled` 是空函数（占位符） |
| L-03 | `agent/compress.go` | `Compress` 同时承担压缩和消息发送，应分离 |
| L-04 | `agent/progress.go` | `ProgressEvent` 仅使用第一行文本，结构化信息浪费 |
| L-05 | `agent/reply.go` | `ExtractFinalReply` 逻辑过于简化（>500字取最后一段） |
| L-06 | `agent/command_builtin.go` | `/version` 不含 git commit hash |
| L-07 | `agent/trigger.go` | `triggerProviders` sync.Map 无清理机制 |
| L-08 | `agent/quality.go` | `KeyInfoFingerprint` 未被 Phase 1 使用 |
| L-09 | `tools/card_builder.go` | 会话过期清理仅在 CreateSession 时触发 |
| L-10 | `tools/card_tools.go` | `cardToolNames` 硬编码列表易遗漏 |
| L-11 | `tools/hook.go` | HookChain 未限制 hook 数量 |
| L-12 | `tools/logs.go` | 日志目录路径硬编码 `.xbot/logs` |
| L-13 | `tools/read.go` | 大文件无分页读取 |
| L-14 | `tools/skill.go` | skill 搜索返回所有匹配文件，无数量限制 |
| L-15 | `tools/oauth.go` | OAuth scopes 参数未验证合法性 |
| L-16 | `tools/path_guard.go` | ResolveWritePath 与 ValidateReadPath 行为不对称 |
| L-17 | `bus/bus.go` | MessageBus channel 容量硬编码为 64 |
| L-18 | `oauth/manager.go:254` | `ListProviders` 返回无序列表 |
| L-19 | `oauth/manager.go:244` | `generateState` 仅 16 字节（建议 32 字节） |
| L-20 | `version/version.go` | `Info()` 无测试 |

### 3.2 防御性编程 / 边界情况 (12 个)

| # | 文件 | 问题 |
|---|------|------|
| L-21 | `channel/feishu.go` | `limitMarkdownTables` 表格以 separator 行开头时边界错误 |
| L-22 | `channel/qq.go` | `downloadOneBotImage` 50MB 限制过大（建议 10-20MB） |
| L-23 | `channel/onebot.go` | `stripQQMention` 是 O(n²) 但实际消息短，影响极小 |
| L-24 | `llm/retry.go` | `isInputTooLongError` 匹配 "400" 过于宽松 |
| L-25 | `llm/mock.go` | Generate 和 GenerateStream 行为不一致 |
| L-26 | `llm/openai.go` | `buildThinkingOptions` 未知模式静默忽略 |
| L-27 | `tools/cd.go` | 沙箱模式下 CurrentDir 使用宿主机路径做 Prefix 检查，语义不清晰 |
| L-28 | `tools/glob.go` | `globToFindArgs` 不处理含 `\` 的 pattern（已有 filepath.ToSlash 保护） |
| L-30 | `tools/workspace_scope.go` | `SanitizeWorkspaceKey` 未限制用户 ID 长度 |
| L-31 | `tools/offload_recall.go` | `offloadMaxLimit = 16000` 无注释说明选择依据 |
| L-32 | `tools/feishu_mcp/block_type_map.go` | Heading5~Heading9 常量可能是死代码 |

### 3.3 测试 / 文档 (10 个)

| # | 文件 | 问题 |
|---|------|------|
| L-33 | `tools/chat_history.go` | 无测试覆盖 |
| L-34 | `tools/feishu_mcp/docx_test.go` | 使用 `//go:build ignore` 标签，测试永远不执行 |
| L-35 | `tools/feishu_mcp/docx_write.go` | `cleanBlockForDescendant` 仅处理 Mermaid 特殊 case，缺注释 |
| L-36 | `storage/vectordb/archival.go` | `SearchByDocumentContains` 参数位置需确认 API 签名 |
| L-37 | `cron/scheduler.go` | `Start()` 和 `StartDelayed()` 共用 once，缺少文档说明 |
| L-38 | `pprof/pprof.go:103` | `/debug/gc` 无认证，可触发 STW |
| L-39 | `storage/sqlite/db.go` | Schema 迁移无测试 |
| L-40 | `storage/vectordb/archival.go` | `newOllamaEmbedFunc` 中 `checkedNormalized` 闭包变量内存屏障不够清晰 |
| L-41 | `storage/sqlite/migrate.go:200-202` | `migrateSchema` 版本跳跃缺少严格一致性检查 |
| L-42 | `logger/logger.go:171` | `cleanupOldLogs` goroutine 无退出机制 |

### 3.4 其他 (10 个)

| # | 文件 | 问题 |
|---|------|------|
| L-43 | `agent/rand.Intn` | 使用 math/rand 而非 crypto/rand（Go 1.20+ 已自动 seed，无安全影响） |
| L-44 | `agent/engine.go` | `toolCallEntry` 缺少来源追踪（iteration/tool call index） |
| L-45 | `tools/cron.go` | `at` 参数未验证 ISO 格式 |
| L-46 | `tools/edit.go` | regex 模式仅做 regexp.Compile，RE2 保证线性时间但未限制匹配步数 |
| L-47 | `tools/grep.go` | sandbox grep 输出解析用 `-` 分隔符不精确 |
| L-48 | `tools/feishu_mcp/download.go` | `mcp_info` 接口使用 tenant_access_token 无用户级鉴权（同 S-08） |
| L-49 | `channel/feishu.go` | `extractPostText` 中 `messageId` 参数传递链不太直观 |
| L-50 | `tools/search_tools.go` | 向量搜索结果已限制 limit=5，当前实现合理 |
| L-51 | `tools/sandbox_runner.go` | Docker 镜像名使用 userID 构造，需校验格式 |
| L-52 | `memory/memory.go` | 纯接口定义，设计清晰，无问题 |

---

## 四、各模块审核详情

### 4.1 agent/ 模块（55 文件，17,232 行）

**审核结论**: 核心 Agent 引擎，代码量最大。并发安全是主要关注点。

**高优问题**: panic 吞没(H-01)、启动竞态(H-02)、命令注入(H-03)、运行时 panic(H-04)、goroutine 泄露(H-05)、缓存无上限(H-06)

**测试覆盖**: 
- ✅ engine_test.go (517行)、offload_test.go (611行)、topic_test.go (478行) 覆盖较好
- ❌ agent.go（1820行核心文件）完全无单元测试
- ❌ llm_factory.go、registry.go、context.go 缺少测试

### 4.2 tools/ 模块（75 文件，21,469 行）

**审核结论**: 工具层代码量最大，安全风险最集中。

**高优问题**: SSRF 绕过、命令注入、IPv6 未拦截、文件名颠倒

**安全重点**: 
- `fetch.go`: SSRF 防护存在 DNS 重绑定和 IPv6 绕过
- `glob.go`/`grep.go`: shell 命令构造需严格转义
- `shell.go`: 命令日志脱敏
- `feishu_mcp/`: 下载/查询接口缺少鉴权

**测试覆盖**:
- ✅ path_guard、cd、glob、grep、fetch、hook、shell、sandbox 有良好测试
- ❌ cron、download、chat_history、card_tools、session_mcp、skill、web_search、feishu_mcp 整体缺测试

### 4.3 channel/ 模块（11 文件，7,585 行）

**审核结论**: 消息通道层，并发安全和权限检查是关键。

**高优问题**: onCardAction 权限绕过(C-H3)、nil 指针风险(C-H2)、isDuplicate 锁问题(C-H1)、sendAutoDetect 无效调用(C-H4)

**测试覆盖**:
- ✅ feishu_settings、capability、retry 有良好测试
- ❌ feishu.go（最复杂文件 ~800 行）零测试 — 这是最大风险
- ❌ dispatcher.go 无测试

### 4.4 storage/ 模块（21 文件，5,002 行）

**审核结论**: 数据持久化层，SQL 安全和并发访问是关注点。

**高优问题**: API Key 明文(H1)、FTS 触发器缺失(H2)、WAL/busy_timeout 缺失(H3)、TOCTOU 竞态(H4)

**测试覆盖**:
- ✅ session、memory、core_memory、registry、tenant、user_settings、recall 有良好测试
- ❌ db.go（schema + 13 版本迁移）无测试
- ❌ user_llm_config、user_profile、cron、archival、migrate 缺测试

### 4.5 llm/ 模块（11 文件，3,057 行）

**审核结论**: LLM 接口层，retry 逻辑设计精良是亮点。

**高优问题**: Anthropic 429/5xx 不重试(L-H1)

**亮点**: retry.go — perAttemptCtx 正确处理超时重试，isRetryableError 正确区分错误类型

**测试覆盖**:
- ✅ retry.go 测试优秀、openai_thinking/anthropic_thinking 有测试
- ❌ types.go、tokenizer.go、mock.go 缺测试
- ⚠️ openai/anthropic 核心方法依赖 SDK，仅有部分测试

### 4.6 其他模块（22 文件，~5,400 行）

**审核结论**: 基础设施层，OAuth 模块安全问题最突出。

**高优问题**: OAuth 0.0.0.0 绑定(H-01)、Flow 内存泄漏(H-02)、Token 明文(H-04)、SendFunc 竞态(H-03)

**测试覆盖**:
- ✅ logger_test.go、session_test.go、bus/address_test.go 覆盖良好
- ❌ oauth/ 整体无测试（Manager、Server、Storage、Provider）
- ❌ cron/scheduler.go 无测试
- ❌ config.go 无测试

---

## 五、测试覆盖评估

### 5.1 按模块统计

| 模块 | 源文件 | 有测试 | 无测试 | 覆盖率 |
|------|--------|--------|--------|--------|
| agent/ | 33 | 22 | 11 | 67% |
| tools/ | ~47 | ~28 | ~19 | 60% |
| channel/ | 7 | 3 | 4 | 43% |
| storage/ | 15 | 7 | 8 | 47% |
| llm/ | 7 | 4 | 3 | 57% |
| 其他 | ~11 | 4 | 7 | 36% |
| **合计** | **~120** | **~68** | **~52** | **57%** |

### 5.2 关键缺失测试的文件（按优先级排序）

| 优先级 | 文件 | 原因 |
|--------|------|------|
| P0 | `channel/feishu.go` | 最复杂消息处理文件（~800行），权限/去重/卡片回调零测试 |
| P0 | `storage/sqlite/db.go` | Schema + 13 版本迁移逻辑无测试 |
| P0 | `oauth/manager.go` | OAuth 核心流程（StartFlow/CompleteFlow/Token 刷新）零测试 |
| P1 | `agent/agent.go` | 核心文件 1820 行无单元测试 |
| P1 | `tools/feishu_mcp/*.go` | 飞书 MCP 工具整体零测试（14 个文件） |
| P1 | `cron/scheduler.go` | 定时任务调度逻辑复杂，零测试 |
| P2 | `tools/download.go` | 文件下载 + 外部 API 调用无测试 |
| P2 | `tools/cron.go` | Cron 工具 CRUD 无测试 |
| P2 | `oauth/server.go` + `storage.go` | OAuth 端点和存储零测试 |
| P2 | `tools/session_mcp.go` | MCP 连接管理零测试 |
| P3 | `tools/web_search.go` | Web 搜索无测试 |
| P3 | `tools/skill.go` + `skill_sync.go` | 技能加载/同步无测试 |
| P3 | `llm/tokenizer.go` | Token 计数无测试 |

---

## 六、跨模块系统性问题

### 6.1 🔴 需立即修复

| # | 系统性问题 | 涉及模块 | 影响 |
|---|-----------|----------|------|
| 1 | **敏感数据明文存储** — API Key、OAuth Token 均以明文存入 SQLite | storage/sqlite, oauth | 数据泄露风险 |
| 2 | **权限绕过** — onCardAction 未做 AllowFrom 检查 | channel/feishu | 任何用户可发送消息 |
| 3 | **SSRF 防护不完整** — DNS 重绑定 + IPv6 内网未拦截 | tools/fetch | 内网服务暴露 |

### 6.2 🟡 应短期修复

| # | 系统性问题 | 涉及模块 | 影响 |
|---|-----------|----------|------|
| 4 | **内存泄漏模式** — 多处 map/cache 从不清理（LLM 缓存、consolidating、OAuth Flow、chat_history、triggerProviders） | agent, tools, oauth | 长期运行 OOM |
| 5 | **SQLite 并发安全** — 缺少 WAL 模式、busy_timeout、TOCTOU 竞态 | storage/sqlite | 高并发下数据损坏 |
| 6 | **goroutine 泄漏** — cleanupOldLogs 无退出机制、启动阶段 Sleep 竞态 | logger, agent | 资源泄漏 |
| 7 | **Anthropic 错误不重试** — 429/5xx 错误因格式不匹配而跳过重试 | llm/retry | 用户体验差 |
| 8 | **panic 用于生产断言** — compress.go 和 middleware.go 的 assert panic | agent | 单条消息可导致服务不可用 |
| 9 | **FTS5 索引不一致** — 只有 INSERT 触发器，缺 DELETE/UPDATE | storage/sqlite | 搜索结果含已删除数据 |
| 10 | **OAuth 安全策略不一致** — server 绑定 0.0.0.0 而 pprof 绑定 localhost | oauth, pprof | 攻击面不一致 |

### 6.3 🟢 长期改进

| # | 系统性问题 | 涉及模块 | 影响 |
|---|-----------|----------|------|
| 11 | **时间格式不统一** — RFC3339 vs 本地格式 vs UTC 混用 | storage/sqlite | 时间解析错误 |
| 12 | **代码重复** — parseTimestamp、buildRunConfig 等多处重复 | 多模块 | 维护风险 |
| 13 | **feishu_mcp 文件命名颠倒** — search.go 是 wiki，wiki.go 是 bitable | tools/feishu_mcp | 可维护性 |
| 14 | **feishu.go 测试覆盖为零** — 最复杂的消息处理文件无测试 | channel | 质量风险 |
| 15 | **大量 sync.Map 无清理机制** — 用户体验数据长期积累 | agent, tools | 内存增长 |

---

## 七、优先修复建议

### Phase 1: 立即修复（安全关键，1-2 天）

| 序号 | 问题编号 | 修复内容 | 预计工作量 |
|------|----------|----------|-----------|
| 1 | S-01 | onCardAction 增加 isAllowed 权限检查 | 0.5h |
| 2 | S-02 + S-03 | fetch.go SSRF 防护：增加 DNS 解析 + IPv6 检查 | 1h |
| 3 | S-04 + S-05 | API Key / OAuth Token 加密存储 | 4h |
| 4 | S-06 | OAuth Server 默认绑定 localhost | 0.5h |
| 5 | S-12 | glob.go shellEscape 转义 | 1h |

### Phase 2: 短期修复（稳定性关键，1 周）

| 序号 | 问题编号 | 修复内容 | 预计工作量 |
|------|----------|----------|-----------|
| 6 | B-01 + R-01 + R-02 | 将 panic 替换为 error 返回 | 2h |
| 7 | B-05 | retry.go 增加 Anthropic 错误格式匹配 | 0.5h |
| 8 | H-03 | SQLite 设置 WAL + busy_timeout | 0.5h |
| 9 | B-06 | GetOrCreateTenantId 改用 INSERT OR IGNORE | 1h |
| 10 | R-03 | OAuth CleanupExpiredFlows 定时调用 | 0.5h |
| 11 | H-02 | FTS5 补充 DELETE/UPDATE 触发器 | 1h |
| 12 | B-03 + B-04 | feishu.go nil guard + 锁分离 | 2h |

### Phase 3: 中期改进（质量提升，2-4 周）

| 序号 | 修复内容 | 预计工作量 |
|------|----------|-----------|
| 1 | 清理内存泄漏：LLM 缓存 LRU、consolidating map、chat_history 限制、triggerProviders TTL | 4h |
| 2 | 补充关键测试：feishu.go、db.go、oauth/manager.go | 8h |
| 3 | Q-01 文件重命名：search.go → wiki.go、wiki.go → bitable.go | 2h |
| 4 | main.go 修复：OAuth DB 关闭、启动通知就绪信号 | 2h |
| 5 | cron 性能：优先队列替代每秒全量扫描 | 4h |

### Phase 4: 长期优化（持续改进）

| 序号 | 修复内容 |
|------|----------|
| 1 | 统一时间格式（全库使用一种格式） |
| 2 | 提取公共工具函数（parseTimestamp、buildBaseRunConfig） |
| 3 | agent.go 拆分 New() 函数 |
| 4 | 补充剩余模块测试覆盖至 80%+ |
| 5 | S-04/S-05 考虑迁移到 sqlcipher 加密整个数据库 |

---

## 审核总结

xbot 是一个功能丰富、架构合理的多渠道 AI Agent 框架。代码整体质量**中上**，有良好的接口抽象和模块化设计。**retry.go** 的重试逻辑、**qq.go** 的并发控制、**path_guard.go** 的安全守卫都是代码质量的亮点。

**主要风险集中在三个领域**：

1. **安全** — 敏感数据明文存储、SSRF 防护不完整、权限绕过（需立即修复）
2. **资源管理** — 多处内存泄漏、goroutine 泄漏（影响长期运行稳定性）
3. **测试覆盖** — 关键模块（feishu.go、db.go、oauth）零测试（质量风险）

建议按 Phase 1 → Phase 4 的优先级逐步修复，其中 **Phase 1 的安全问题建议在下一版本发布前完成**。

---

*本报告由门下省审核，太子汇总复奏。审核基准 commit: `879bd5a`。*
