项目: xbot — 多渠道 AI Agent 框架
审核范围: 全部 Go 源文件(197 个文件,60,465 行代码)
基准 Commit: 879bd5a(master 分支)
审核日期: 2026-03-21
审核机构: 门下省(太子分批派发,6 批并行审核)
| 模块 | 文件数 | 代码行数 | 职责 |
|---|---|---|---|
| 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 文件 |
| # | 文件 | 行号 | 问题 | 建议 |
|---|---|---|---|---|
| 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 |
| # | 文件 | 行号 | 问题 | 建议 |
|---|---|---|---|---|
| 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 传入 |
| # | 文件 | 问题 | 建议 |
|---|---|---|---|
| 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 |
| # | 文件 | 问题 | 建议 |
|---|---|---|---|
| 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 |
| # | 文件 | 问题 | 建议 |
|---|---|---|---|
| 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 |
| # | 文件 | 问题 | 建议 |
|---|---|---|---|
| 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 |
| # | 文件 | 问题 | 建议 |
|---|---|---|---|
| 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 提到过时工具名 | 更新与实际工具名称一致 |
| # | 文件 | 问题 |
|---|---|---|
| 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() 无测试 |
| # | 文件 | 问题 |
|---|---|---|
| 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-29 | tools/shell_env.go | parseEnvFileLines 行为正确但建议添加注释 |
| 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 常量可能是死代码 |
| # | 文件 | 问题 |
|---|---|---|
| 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 无退出机制 |
| # | 文件 | 问题 |
|---|---|---|
| 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 | 纯接口定义,设计清晰,无问题 |
审核结论: 核心 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 缺少测试
审核结论: 工具层代码量最大,安全风险最集中。
高优问题: 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 整体缺测试
审核结论: 消息通道层,并发安全和权限检查是关键。
高优问题: onCardAction 权限绕过(C-H3)、nil 指针风险(C-H2)、isDuplicate 锁问题(C-H1)、sendAutoDetect 无效调用(C-H4)
测试覆盖:
- ✅ feishu_settings、capability、retry 有良好测试
- ❌ feishu.go(最复杂文件 ~800 行)零测试 — 这是最大风险
- ❌ dispatcher.go 无测试
审核结论: 数据持久化层,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 缺测试
审核结论: 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,仅有部分测试
审核结论: 基础设施层,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 无测试
| 模块 | 源文件 | 有测试 | 无测试 | 覆盖率 |
|---|---|---|---|---|
| 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% |
| 优先级 | 文件 | 原因 |
|---|---|---|
| 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 计数无测试 |
| # | 系统性问题 | 涉及模块 | 影响 |
|---|---|---|---|
| 1 | 敏感数据明文存储 — API Key、OAuth Token 均以明文存入 SQLite | storage/sqlite, oauth | 数据泄露风险 |
| 2 | 权限绕过 — onCardAction 未做 AllowFrom 检查 | channel/feishu | 任何用户可发送消息 |
| 3 | SSRF 防护不完整 — DNS 重绑定 + IPv6 内网未拦截 | tools/fetch | 内网服务暴露 |
| # | 系统性问题 | 涉及模块 | 影响 |
|---|---|---|---|
| 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 | 攻击面不一致 |
| # | 系统性问题 | 涉及模块 | 影响 |
|---|---|---|---|
| 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 | 内存增长 |
| 序号 | 问题编号 | 修复内容 | 预计工作量 |
|---|---|---|---|
| 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 |
| 序号 | 问题编号 | 修复内容 | 预计工作量 |
|---|---|---|---|
| 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 |
| 序号 | 修复内容 | 预计工作量 |
|---|---|---|
| 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 |
| 序号 | 修复内容 |
|---|---|
| 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 的安全守卫都是代码质量的亮点。
主要风险集中在三个领域:
- 安全 — 敏感数据明文存储、SSRF 防护不完整、权限绕过(需立即修复)
- 资源管理 — 多处内存泄漏、goroutine 泄漏(影响长期运行稳定性)
- 测试覆盖 — 关键模块(feishu.go、db.go、oauth)零测试(质量风险)
建议按 Phase 1 → Phase 4 的优先级逐步修复,其中 Phase 1 的安全问题建议在下一版本发布前完成。
本报告由门下省审核,太子汇总复奏。审核基准 commit: 879bd5a。