# aibox 模块安全与质量审计报告 - **日期**:2026-08-17 - **审计基线**:HEAD `ce142fe`(Batch A–D 落地后) - **审计范围**:`aibox.py`(~3150 行)+ `llm_client.py` + `tools/`(7 文件)+ `subsystems/`(6 文件)+ `common/` - **方法**:逐文件代码审读 + PoC 动态验证(H2 已实证) --- ## 一、总体结论 架构设计(分层、参数化 SQL、密钥隔离、消息回滚)整体健康,但**命令执行链存在一条系统性断裂**:实际运行路径绕过了精心设计的执行器,导致 1MB 硬约束、超时、中断三项保护同时失效。 --- ## 二、高危发现(3 项) ### H1:`run_command` 实际路径绕过注入的 executor——三重保护同时失效 证据链: - `aibox.py:1012` 将 `command_executor` 注入 `CommandLineTool` - 但 `tools/command_tool.py:86-219` 的 `execute_streaming` 自带一套 **150 行的重复 Popen 实现**,`self.executor` 全程未使用(死注入) - `tools/registry.py:71-86` 检测到 `execute_streaming` 即走该路径——**生产 100% 命中** 后果: | 保护 | 设计位置 | 实际状态 | |---|---|---| | 1MB 输出上限(硬约束) | `executors.py:69` | **失效**——command_tool 的 `output_lines` 无限累积 | | `cancel()` 中断 | `executors.py:76-86` 只 kill `_current_process` | **失效**——实际进程引用只在 command_tool 局部变量,三层中断的第三层对 run_command 不生效 | | 超时/截断统一语义 | executors | 旁路(见 H2) | 附带问题:`msvcrt.kbhit()` 检查的是**当前进程**键盘而非子管道(`command_tool.py:123-125` 无意义代码);循环内重复 `import select`。 ### H2:超时机制对无输出命令完全失效(PoC 已证) `readline()` 是阻塞调用,超时检查只在**两次 readline 之间**执行。无输出命令会让 readline 阻塞到进程自然退出: ``` PoC:timeout=3s,命令 powershell Start-Sleep 15(无 stdout 输出) 结果:readline blocked 15.2s (timeout was 3s) VERDICT: timeout mechanism ineffective for silent commands: True ``` 两处同病:`command_tool.py:106-165` 与 `executors.py:97-113`。`--once` 模式有 `--once-timeout` 兜底,**WebSocket 模式无兜底**。 ### H3:`stdin=subprocess.PIPE` 从不写入也不关闭——死锁放大器 `executors.py:156`、`command_tool.py:93`:等待 stdin 的命令(`cat` 无参、交互式 `python`、`ssh`)永不退出 → 与 H2 叠加:**readline 永久阻塞 + 超时检测永不执行 + cancel 杀不到进程** = 工具线程永久挂起。应为 `stdin=subprocess.DEVNULL`。 --- ## 三、中危发现(4 项) | # | 问题 | 位置 | 说明 | |---|---|---|---| | M1 | **heredoc 定界符注入** | `basic_tools.py:179` | `content` 含独立行 `AIBOX_EOF` 时 heredoc 提前终止,**后续行成为 shell 命令执行**。AI 写文件内容是不可信输入(可被网页内容/用户消息诱导)。修复:定界符唯一化(检测冲突则换 `AIBOX_EOF_2` 递增) | | M2 | 工具流式转发循环可永久挂起 | `registry.py:90-107` | `while thread.is_alive() or not empty()` + `get(0.1)`——H3 场景下永不退出;`join(5)` 超时后返回但 daemon 线程继续向输出队列写(僵尸输出混入后续对话流) | | M3 | web 工具无协议/目标校验 | `basic_tools.py:315-338` | `url` 未限制 scheme,`curl file:///etc/shadow`、内网地址均可达(SSRF)。本地单用户产品风险有限,建议白名单 http/https | | M4 | shlex.quote POSIX 转义在 Windows 失效 | `basic_tools.py:99-112` 等全部指引命令 | 单引号在 cmd.exe 无效,Windows 下 `head/sed/grep` 指引命令执行异常。Linux 部署(树莓派)不受影响 | --- ## 四、低危发现(3 项) | # | 问题 | 位置 | |---|---|---| | L1 | `get_pending_memos` 的 `LIMIT {limit}` f-string 拼接 | `db.py:417`——当前调用链 limit 均为内部 int,无外部直达;防御性参数化即可 | | L2 | `check_path_safety` 仅查大小,无路径边界 | `base.py:96-110`——by design(AI 可全盘读写,本地单用户),已确认设计意图 | | L3 | `executors.execute()` finally kill 后未 `wait()` | `executors.py:205-207`——Windows 句柄短暂滞留 | **做得好的**:SQL 全面参数化(LIKE 通配符正确转义 `db.py:200-209`)、API key 零日志泄漏、WAL 已启用、消息回滚/tool 配对修复、`_msg_lock`/`_process_lock` 使用正确、工具实例按连接隔离。 --- ## 五、架构合规矩阵 | 硬约束 | 状态 | |---|---| | WebSocket :8765 | ✅ | | 依赖单向 `common←subsystems←tools←aibox` | ✅ | | Worker 仅 7 执行工具 | ✅ | | 工具输出 ≤1MB + 超时 + 中断 | ❌ **H1/H2/H3 三项全破** | | prompt 三级排序加载 / sqlite 线程安全 | ✅ | --- ## 六、修复建议(一批搞定 H1/H2/H3,约 ±120 行) 1. **删除 command_tool.py 的重复 Popen 实现**,`execute_streaming` 改为一行委托 `self.executor.execute_streaming(output_queue, ...)` → 1MB、cancel、统一语义全部自动恢复 2. **executors.py 读取循环改线程化**:后台 reader 线程做阻塞 `readline` 入队列,主循环带超时轮询队列——跨平台解决 H2 3. `stdin=subprocess.DEVNULL`(H3) 4. M1 定界符唯一化 + M2 转发循环加总超时,顺手可修 --- ## 七、修复落地(Batch E,2026-08-17) 五项修复(H1/H2/H3/M1/M2)已全部实施并通过验证: | # | 修复 | 位置 | 状态 | |---|---|---|---| | H1 | `CommandLineTool.execute_streaming` 改为纯委托 `executor.execute_streaming`,删除 150 行重复 Popen(-165 行) | `tools/command_tool.py` | ✅ | | H2 | `_read_with_limit` 线程化:后台 reader 线程阻塞 `readline` 入队,主循环 0.1s 轮询 + 超时/1MB 检查;shell 已死但 reader 无 EOF 时 1s 宽限排空(Windows shell=True 孙进程持管道场景) | `subsystems/executors.py` | ✅ | | H3 | 两处 Popen 均改 `stdin=subprocess.DEVNULL` | `subsystems/executors.py` | ✅ | | M1 | heredoc 定界符唯一化:content 含独立行与 `AIBOX_EOF` 相同时递增 `_X` 后缀直到唯一 | `tools/basic_tools.py` | ✅ | | M2 | registry 转发循环加 3600s 总超时兜底,超时后放弃等待并返回错误信息 | `tools/registry.py` | ✅ | ### PoC 回归验证(`aibox/tests/_poc_audit_regression.py`,5/5 PASS) ``` [PASS] PoC-1 无输出命令超时(H2): 3.0s 返回(修复前阻塞15s+),结果含超时: True [PASS] PoC-2 stdin等待命令不挂起(H3): 0.2s 返回(修复前永久挂起) [PASS] PoC-3 1MB输出截断: 0.2s,结果 1.00MB,含截断标记: True [PASS] PoC-4 cancel中断: cancel 后 3.0s 结束(修复前杀不到进程需等满120s) [PASS] PoC-5 工具层1MB贯通(H1): 0.2s,工具层结果 1.00MB(修复前无限累积) ``` PoC-2 说明:`stdin=DEVNULL` 下等待输入的命令立即收到 EOF 快速退出(0.2s),优于按超时返回;判定标准为"不永久挂起"。 ### 单元测试 aibox 全量单测 **264 passed**(零回归)。 ### 遗留项(本轮未处理) - M3(web 工具 scheme/目标校验)、M4(Windows 下 shlex.quote 转义失效)、L1/L3(防御性加固)——低优先级,待后续批次 - 架构合规矩阵"工具输出 ≤1MB + 超时 + 中断"一项修复后为 ✅