From 1348660cd8739b968b735a0d4eb3237076914f34 Mon Sep 17 00:00:00 2001 From: qing <1776177662@qq.com> Date: Mon, 17 Aug 2026 17:51:43 +0800 Subject: [PATCH] docs(aibox): second-round audit re-review at HEAD 177a9ac - Batch E fixes re-verified: H1/H3/M1 solid, H2 solid but with two residuals (reader thread leak, subagent timeout blind spot), M2 incomplete (timeout paths skip tool attribute cleanup) - Legacy M3/M4/L1/L3 confirmed unfixed; L1 found second instance at db.py:459 (get_immediate_memos) - New findings: 1 high (SOCKS5 proxy password persisted in plaintext), 9 medium (drain thread leak, event-loop blocking RLock, FTS5 keyword injection, streaming bypass of result length cap, persistent prompt injection storage, TOCTOU on mark_as_triggered, etc.), 7 low - Updated compliance matrix and proposed Batch F fix plan (F1-F6) --- docs/reports/aibox_audit_20260817.md | 93 ++++++++++++++++++++++++++-- 1 file changed, 88 insertions(+), 5 deletions(-) diff --git a/docs/reports/aibox_audit_20260817.md b/docs/reports/aibox_audit_20260817.md index 048fac6..78793a2 100644 --- a/docs/reports/aibox_audit_20260817.md +++ b/docs/reports/aibox_audit_20260817.md @@ -1,15 +1,17 @@ # aibox 模块安全与质量审计报告 -- **日期**:2026-08-17 -- **审计基线**:HEAD `ce142fe`(Batch A–D 落地后) -- **审计范围**:`aibox.py`(~3150 行)+ `llm_client.py` + `tools/`(7 文件)+ `subsystems/`(6 文件)+ `common/` -- **方法**:逐文件代码审读 + PoC 动态验证(H2 已实证) +- **日期**:2026-08-17(第一轮)/ 2026-08-18(第二轮复审) +- **审计基线**:第一轮 HEAD `ce142fe`(Batch A–D 后)→ 第二轮 HEAD `177a9ac`(Batch E 修复后) +- **审计范围**:`aibox.py`(~3026 行)+ `llm_client.py` + `tools/`(7 文件)+ `subsystems/`(6 文件)+ `common/`(5 文件) +- **方法**:第一轮逐文件代码审读 + PoC 动态验证;第二轮 Batch E 修复复审 + 全量重审 + 6 处关键发现实证抽查 --- ## 一、总体结论 -架构设计(分层、参数化 SQL、密钥隔离、消息回滚)整体健康,但**命令执行链存在一条系统性断裂**:实际运行路径绕过了精心设计的执行器,导致 1MB 硬约束、超时、中断三项保护同时失效。 +**第一轮**(基线 ce142fe):架构设计(分层、参数化 SQL、密钥隔离、消息回滚)整体健康,但**命令执行链存在一条系统性断裂**:实际运行路径绕过了精心设计的执行器,导致 1MB 硬约束、超时、中断三项保护同时失效。 + +**第二轮**(基线 177a9ac):Batch E 五项修复(H1/H2/H3/M1/M2)中四项确认修复到位(PoC 5/5 验证),**M2 修复不完整**(超时路径跳过工具属性清理,僵尸输出问题仍存)。第二轮全量重审新发现 **1 高危 + 9 中危 + 若干低危**,其中三项与 Batch E 修复直接相关(reader 线程泄漏 / 子代理超时盲区 / M2 残留),其余为存量问题(代理密码明文落盘、drain 线程泄漏、事件循环阻塞等)。详见"八、第二轮复审"。 --- @@ -126,3 +128,84 @@ aibox 全量单测 **264 passed**(零回归)。 - M3(web 工具 scheme/目标校验)、M4(Windows 下 shlex.quote 转义失效)、L1/L3(防御性加固)——低优先级,待后续批次 - 架构合规矩阵"工具输出 ≤1MB + 超时 + 中断"一项修复后为 ✅ + +--- + +## 八、第二轮复审(2026-08-18,HEAD `177a9ac`) + +方法:Batch E 修复质量复审 + 全量重审(3 路并行审读 + 主审对 6 处关键发现逐一实证抽查)。 + +### 8.1 Batch E 修复复审结论 + +| # | 修复 | 复审结论 | +|---|---|---| +| H1 | command_tool 纯委托 executor | ✅ 到位(PoC-5 验证 1MB 贯通) | +| H2 | reader 线程 + 超时轮询 | ✅ 到位(PoC-1 验证),**但有两个残余**:N4 reader 线程泄漏、N5 子代理超时盲区 | +| H3 | stdin=DEVNULL | ✅ 到位(PoC-2 验证) | +| M1 | heredoc 定界符唯一化 | ✅ 到位 | +| M2 | registry 转发总超时 | ⚠️ **不完整**(见 N6) | + +### 8.2 第一轮遗留项状态 + +| # | 问题 | 状态 | +|---|---|---| +| M3 | web 工具无 scheme 校验 | 未修复(实证:`basic_tools.py:319+` url 未限制,`file://` 等可达) | +| M4 | shlex.quote POSIX 转义 Windows 失效 | 未修复(实证:`sed -r`/`grep -oP` 指引,Linux 部署不受影响) | +| L1 | `LIMIT {limit}` f-string | 未修复,且**新发现同型第二处**(`db.py:417` 与 `db.py:459` `get_immediate_memos`) | +| L2 | check_path_safety 无路径边界 | 维持 by design(本地单用户,AI 可全盘读写为设计意图) | +| L3 | execute() finally kill 后未 wait() | 未修复(实证:`executors.py:236-241` 无 wait;`cancel()` 同样无 wait) | + +### 8.3 新发现——高危(1 项) + +#### N1:SOCKS5 代理密码明文持久化到配置文件 + +`common/config.py:238-251`(实证):`SetProxyTool.execute` 默认 `save=True` → `set_proxy` 将 `password` 明文写入 `_config["proxy"]["socks5"]` → `_save_config` 用 `json.dump` 全量落盘 `~/.aibox/config.json`。与 api_key 走环境变量的隔离设计形成反差,任何能读该文件的进程可获取代理凭据。 + +修复建议:密码不落盘(仅内存)或走系统凭据库;必须落盘时对称加密 + `os.chmod(0o600)`;`save` 默认改 `False`。 + +### 8.4 新发现——中危(9 项) + +| # | 问题 | 位置 | 说明 | +|---|---|---|---| +| N2 | drain_thread 每次工具调用泄漏一个永不退出的线程 | `aibox.py:1232-1243`(实证) | 循环条件只查 cancel 事件不查队列空,注释与实现不符;闭包持 output_callback(含 websocket)阻碍 GC;长期运行累积线程/内存 | +| N3 | 同步 threading.RLock 在 async 协程中阻塞事件循环 | `aibox.py:1855-1902, 2275, 2376` | `/new` 的 2s 轮询竞态窗口后仍可能 `with self._msg_lock:` 冻结整个事件循环 → 服务假死 | +| N4 | reader 线程泄漏(H2 残余) | `subsystems/executors.py:130-144` | 宽限 1s break 后 reader 仍阻塞 readline(Windows shell=True 孙进程持管道),stdout 未 close、reader 未 join,常驻服务累积 fd 与线程 | +| N5 | --once 超时对子代理完全无效(H2 盲区) | `aibox.py:3052-3058, 638-649` | Timer 到点只 `manager.cancel()` 父代理;子代理独立 `_cancel_event`/独立 executor/独立 HTTP client,cancel 不传播,进程挂起至子代理自行结束 | +| N6 | registry 超时路径跳过工具属性清理(M2 残余) | `tools/registry.py:99, 116`(实证 L114-120) | 两个超时 return 均跳过 `streaming_output=False`/`output_queue=None`;daemon 线程继续写实例属性 `tool.output_queue`,该属性被后续调用覆盖到新会话队列 → 僵尸输出串流 | +| N7 | FTS5 关键词注入 | `subsystems/db.py:179` | 关键词内 `"` 未转义(应翻倍),可破坏短语边界改变 AND/OR 语义;语法错误时回退 LIKE,影响有限 | +| N8 | 流式输出旁路 TOOL_MAX_RESULT_LENGTH | `tools/db_tools.py:97-98` + `registry.py:104-110` | 超长记忆 content 经流式通道完整推到前端,100000 字符上限仅约束非流式返回值 | +| N9 | prompt 注入存储 | `tools/prompt_tools.py:64, 127, 227` | Agent 可控内容原样写入 `00_IDENTITY.md`/`99_SOUL.md`/`100_STATUS.md` 并在后续会话作为系统提示词加载,形成持久化注入;update_soul 可自我强化 | +| N10 | mark_as_triggered TOCTOU 竞态 | `subsystems/db.py:509-570` | 跨连接先读后写,并发触发 trigger_count 失真;建议 BEGIN IMMEDIATE 同事务读写 | + +### 8.5 新发现——低危(7 项) + +| # | 问题 | 位置 | 说明 | +|---|---|---|---| +| N11 | debug 日志打印工具敏感参数 | `tools/registry.py:66, 126` | debug=3 时 set_proxy 的 password 明文进 `~/.aibox/ai.log`(日志无 0600) | +| N12 | _load_persona 路径遍历(限 .md) | `aibox.py:527-543` | persona 参数 LLM 可控未净化,可读 SKILLS_DIR 外 .md 文件并注入子代理 | +| N13 | broadcast 裸 except + unregister KeyError 逃逸 | `aibox.py:2076-2082, 1971-1973` | 吞 CancelledError;并发注销时 `remove` 抛 KeyError;建议 `discard` + `except Exception` | +| N14 | _processing_tasks 按 ws id 覆盖,旧任务失联 | `aibox.py:2728-2742` | 连接关闭只能 cancel 最后一个任务;handle_command 不受 is_processing 保护 | +| N15 | llm_client 异常含完整响应体 | `llm_client.py:172-174` | 某些网关 4xx 页回显 Authorization 头 → 密钥进异常栈;建议截断 500 字符 | +| N16 | 历史索引裸 except + 归档文件名撞名覆盖 | `subsystems/conversation_history.py:60-66, 135-137` | Ctrl+C 被吞后空索引覆盖原文件;时间戳粒度不足同秒覆盖 | +| N17 | InstallSkillTool skill_path 无校验 | `tools/skill_tools.py:252-304` | 可引导复制任意敏感文件进技能目录,经 search_skills show_content 预览泄露 | + +杂项(低危,随批处理):`executors.py:209/301/338` 裸 except 应收窄为 TimeoutExpired;`db.py` 多处 `with sqlite3.connect` 不显式 close(CPython 下无害,PyPy 下句柄延迟);`config.py:31-34` AI_CONFIG_PATH 可重定向配置加载(本地单用户风险有限);skill_manager 技能目录代码执行面维持 by design(建议文档化可信边界)。 + +### 8.6 更新后的架构合规矩阵 + +| 硬约束 | 第一轮 | 第二轮 | +|---|---|---| +| WebSocket :8765 | ✅ | ✅ | +| 依赖单向 `common←subsystems←tools←aibox` | ✅ | ✅ | +| Worker 仅 7 执行工具 | ✅ | ✅ | +| 工具输出 ≤1MB + 超时 + 中断 | ❌ H1/H2/H3 全破 | ⚠️ **executor 链已恢复**;残余:流式 db_tools 旁路长度上限(N8)、子代理超时无效(N5)、reader 泄漏(N4) | +| prompt 三级排序加载 / sqlite 线程安全 | ✅ | ⚠️ sqlite 主流程安全;mark_as_triggered 竞态(N10)、FTS5 注入(N7)待修 | + +### 8.7 Batch F 修复建议(按优先级) + +1. **F1(高)**:N1 代理密码脱敏——`set_proxy` 落盘前抹除 password 字段 + `save` 默认 False +2. **F2(中)**:N6 registry 超时路径补清理(两个 return 前置 `tool.streaming_output=False; tool.output_queue=None`)+ N4 reader 线程收尾(break 后 `stdout.close()` + `reader.join(2)`) +3. **F3(中)**:N5 子代理 cancel 传播——`run_full_subagent` 传递父 `_cancel_event`,每轮 think 前检查 +4. **F4(中)**:N2 drain_thread 加 sentinel 退出 + N3 reset_conversation 移出事件循环(run_in_executor) +5. **F5(中)**:N7 FTS5 转义 + N8 流式截断 + N10 同事务读写 + 顺带修 L1(两处 LIMIT 参数化)+ L3(finally wait) +6. **F6(低)**:N9 prompt 内容过滤/确认门槛 + N11 日志脱敏 + N12 persona 路径校验 + N13/N14/N15/N16/N17 择机处理 + M3/M4 视部署平台决定