- W13.1 anthropic_plugin (architect-yang, 497 lines): rated C. 6 C ABI functions lack try/catch (§8 violation); my_chat leaks response_body on error path; tool_use response silently dropped. - W13.2 deepseek_plugin (engineer-sun, 486 lines): rated C+. 7 ABI entries unprotected including json::parse paths (malformed JSON terminates); SSE [DONE] sentinel match brittle; ~55% code overlap with anthropic suggests an ai_plugin_base extraction. - W13.3 network_plugin (qa-wang, 322 lines): rated C. CRITICAL: TLS certificate verification fully disabled (set_verify_mode never called, default verify_none accepts any cert) — all AI traffic incl. api_key is MITM-vulnerable. DNS resolve has no timeout; catch lacks (...). - W13.4 lsp_plugin (architect-huang, 749 lines): rated C. CRITICAL: guaranteed deadlock at L519-526 → L547 (g_lsp_impl_start holds mutex then calls g_lsp_impl_stop which re-locks the same non-recursive mutex); 7 vtable funcs unprotected; server→client requests dropped. - W13.5 session+tools (security-cao, 264+251 lines): rated D+/D. Path traversal in builtin_file_read/write (zero validation); global static state in both plugins lacks mutex (UAF risk); 9 vtable funcs lack try/catch. - W13.6 smoke regression (qa-xu, +193 lines): 4 new cases — context max_tokens trim, config dual-store consistency (exposes that W12.2 merge is incomplete: dstalk_config_set→config_service.get returns null), HTTP error path no-crash, repeated init/shutdown cycle. Verified: cmake build 0 error 0 warning, ctest 4/4 pass. Top W14 priorities surfaced: TLS verification (W13.3), LSP deadlock (W13.4), file-tool path traversal (W13.5), config dual-store still broken (W13.6 R2), shared try/catch wrapper across all AI plugins. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
167 lines
8.4 KiB
Markdown
167 lines
8.4 KiB
Markdown
# W13.3 Network Plugin Audit
|
||
|
||
**Auditor**: 王测 (qa-wang)
|
||
**Date**: 2026-05-27
|
||
**File**: plugins/network/src/network_plugin.cpp (322 行)
|
||
**Scope**: plugin-abi.md §2 §3 §5 §8 §9, TLS, timeout, concurrency, resources
|
||
|
||
---
|
||
|
||
## 1. 跨 DLL 堆纪律 (§2 §3)
|
||
|
||
**评级: A (完全合规)**
|
||
|
||
逐行检查:
|
||
- L259: `g_host->strdup(result_body.c_str())` — 正确使用 host 堆分配,符 §3.2 模式 A。调用方 `dstalk_free` 释放。
|
||
- L40-80 `parse_headers_json`: 内部 `std::string` / `unordered_map`,不跨 DLL 边界。
|
||
- L118-123 Lambda capture: 内部 `std::string`,不跨边界。
|
||
- 全文零处裸 `malloc`/`free`/`strdup`/`new`/`delete`。
|
||
|
||
唯一跨边界分配 L259 使用 `g_host->strdup`,正确。
|
||
|
||
## 2. 异常安全 ABI (§5 §8)
|
||
|
||
**评级: B (基础合规,缺 catch(...))**
|
||
|
||
- `on_init` (L296-303): 无线程局部 STL 操作,不会抛异常。安全。
|
||
- `do_post_stream` (L140-254): 整个 I/O 逻辑包裹在 `try { ... } catch (std::exception& e)` (L251)。所有 `std::string`/`unordered_map`/`std::function` 使用均在保护范围内。
|
||
- **缺失**: L251 仅捕获 `std::exception&`,无 `catch(...)` 兜底。符 §8.2 推荐但不符最佳实践(§8.2 正例含 `catch(...)`)。
|
||
- vtable 函数 `http_post_json` / `http_post_stream` 直接调用 `do_post_stream`,间接享受到保护。不直接暴露异常。
|
||
- 若出现非 `std::exception` 派生的异常(SEH / 自定义异常),将穿越 ABI → `std::terminate()`。
|
||
|
||
## 3. 字符串返回值生命周期 (§9)
|
||
|
||
**评级: A**
|
||
|
||
- L248: `result_body = parser.get().body()` — 复制到本地 `std::string`。
|
||
- L252-253: `result_body = e.what()` — `e.what()` 的 C 字符串在 `e` 析构前已复制到 `std::string`,安全。
|
||
- L259: `*response_body = g_host->strdup(result_body.c_str())` — 在函数返回前用 host 堆复制,符 §9.2 模式 A(拥有权转移)。调用方负责释放。
|
||
- 无悬垂指针 / 锁外返回 c_str() / 临时对象 c_str() 等 §9.3 反模式。
|
||
|
||
## 4. TLS 证书验证
|
||
|
||
**评级: F (完全未启用)**
|
||
|
||
```cpp
|
||
// L87-93 — HttpClientCtx 构造函数
|
||
ssl::context ssl_ctx{ssl::context::tlsv12_client};
|
||
HttpClientCtx() {
|
||
ssl_ctx.set_default_verify_paths(); // 设置了 CA 路径...
|
||
// ★ 但从未调用 set_verify_mode()!
|
||
}
|
||
```
|
||
|
||
- **`set_verify_mode(ssl::verify_peer)` 未被调用**: 默认模式为 `ssl::verify_none`,接受**任何**证书(自签名、过期、域名不匹配)。
|
||
- **无 hostname 验证**: 即使启用 `verify_peer`,OpenSSL 本身不做 hostname 匹配。需额外调用 `SSL_set1_host()` 或使用 `boost::asio::ssl::host_name_verification`。代码未做。
|
||
- **SNI 已设置** (L148): `SSL_set_tlsext_host_name` 调用存在,但仅影响 SNI 扩展,不含验证语义。
|
||
- **`set_default_verify_paths` 形同虚设**: CA 路径已配置,但因验证模式未开启,永不使用。
|
||
|
||
**影响**: 所有 AI API 调用 (含 `api_key` 的 Authorization header) 可被中间人任意拦截。CVSS 7.4 (High)。
|
||
|
||
## 5. 超时 (connect / read / write)
|
||
|
||
**评级: C (DNS 无超时可永久 hang)**
|
||
|
||
| 阶段 | 位置 | 超时 | 状态 |
|
||
|------|------|------|------|
|
||
| DNS 解析 | L142 `resolver.resolve()` | 无 | **缺失** — DNS 无响应则永久阻塞 |
|
||
| TCP 连接 | L154-156 | `connect_timeout` (默认 30s) | OK |
|
||
| SSL 握手 | L160-163 | `connect_timeout` (默认 30s) | OK |
|
||
| HTTP 写入 | L180-183 | `request_timeout` (默认 120s) | OK |
|
||
| HTTP 读头 | L188-191 | `request_timeout` (默认 120s) | OK |
|
||
| 流式读体 | L218-219 | `request_timeout` (默认 120s) | OK (每轮循环重设) |
|
||
| 非流式读体 | L241-242 | `request_timeout` (默认 120s) | OK (每轮循环重设) |
|
||
| SSL 关闭 | L250 `stream.shutdown()` | 无 | 低风险(ec 参数,不抛异常) |
|
||
|
||
**关键缺失**: L142 的同步 `resolver.resolve()` 无法设置 socket 级超时(stream 尚未创建)。需改用 `async_resolve` 或在独立线程中执行 resolver。
|
||
|
||
**可配置性**: L129-135 从 config service 读取 `http.connect_timeout` / `http.request_timeout`,有兜底默认值 (30s/120s)。
|
||
|
||
## 6. 错误码语义与重试
|
||
|
||
**评级: B**
|
||
|
||
- L261: `(result_code >= 200 && result_code < 300) ? 0 : -1` — 2xx 成功,其余返回 -1。
|
||
- 调用方可通过 `*status_code` 区分: -1 = 连接/超时/DNS 错误,4xx/5xx = HTTP 错误码。语义正确。
|
||
- **无重试逻辑**: 无任何 retry。对低层 HTTP 客户端这是合理设计,重试应由上层 (AI 插件) 实现。
|
||
- 异常路径 (L251-254): `result_code = -1`, `result_body = e.what()` — 异常消息通过 response_body 传递给调用方,设计合理。
|
||
- L221 / L244: `if (ec) break;` — 流/非流 read_some 错误静默退出循环,错误原因不记录日志(全文零 `g_host->log` 调用)。
|
||
|
||
## 7. 并发安全
|
||
|
||
**评级: A**
|
||
|
||
- 每次 `do_post_stream` 调用创建新 `HttpClientCtx` (L125),内含独立 `io_context`。零共享状态。
|
||
- `g_host` (L33): `on_init` 写入一次,后续只读。`on_shutdown` 不置空(L305-307 为空函数体)。
|
||
- `g_config_svc` (L34): 同上。
|
||
- 无全局 socket / stream / resolver 共享。每个请求自包含。
|
||
- 多线程并发调用 `http_post_json` 安全。
|
||
|
||
## 8. 资源清理
|
||
|
||
**评级: A**
|
||
|
||
- `HttpClientCtx` (L125): 栈上对象,内含 RAII `io_context` + `ssl::context`。析构自动清理。
|
||
- `ssl_stream` (L144), `resolver` (L141), `flat_buffer` (L145): 全部栈上 RAII。
|
||
- 早期退出路径: `goto done` (L150, L214) — 栈展开正确析构所有对象。
|
||
- 异常路径: `catch(std::exception&)` (L251) — 栈展开触发析构。
|
||
- L249 `cancel()` + L250 `stream.shutdown(ec)` — 正常路径优雅关闭。
|
||
|
||
**零泄漏路径**,包括异常和提前退出。无动态分配(除 `g_host->strdup` 的返回值由调用方管理)。
|
||
|
||
## 9. 缓冲区限制 / DoS
|
||
|
||
**评级: A**
|
||
|
||
- L187: `parser.body_limit(16 * 1024 * 1024)` — 16MB 响应体硬上限。防止恶意服务器返回无限大数据导致 OOM。
|
||
- `parse_headers_json` 无长度限制 (L40-80): 理论上调用方可传入超大 JSON,但 headers_json 来源为同进程内其他插件(可信),非网络输入。低风险。
|
||
|
||
---
|
||
|
||
## TOP 3 严重问题
|
||
|
||
### 发现 1 — [严重] TLS 证书验证完全禁用 (L87-93)
|
||
|
||
**问题**: 从未调用 `ssl_ctx.set_verify_mode(ssl::verify_peer)`,默认接受任何证书。无 hostname 验证。所有 API key 和通信内容可被 MITM 拦截。
|
||
|
||
**修复方向**: 添加 `ssl_ctx.set_verify_mode(ssl::verify_peer)` + `ssl_ctx.set_verify_callback(ssl::host_name_verification(host))` 或在 handshake 后调用 `SSL_get_peer_certificate` + `X509_check_host`。
|
||
|
||
### 发现 2 — [高] DNS 解析无超时 (L142)
|
||
|
||
**问题**: `resolver.resolve(host, port)` 同步调用,无法通过 `expires_after` 设置超时(socket 尚不存在)。若 DNS 服务器不响应 → 线程永久阻塞 → 调用方 hang。
|
||
|
||
**修复方向**: 使用 `async_resolve` 并将 io_context 的 `run()` 或 `run_for()` 与超时结合,或在独立 future/promise 线程中执行 resolution 并使用 wait_for。
|
||
|
||
### 发现 3 — [中] 异常处理缺 catch(...) 兜底 (L251)
|
||
|
||
**问题**: 仅捕获 `std::exception&`,无 `catch(...)`。非标准异常 → 穿越 C ABI → `std::terminate()`。虽然 Boost.Beast/Asio 的已知异常均派生自 `std::exception`,但 §8.2 要求双级 catch 全覆盖。
|
||
|
||
**修复方向**: 在 `catch (std::exception&)` 之后追加 `catch (...) { result_code = -1; result_body = "unknown exception"; }`。
|
||
|
||
---
|
||
|
||
## TLS 验证状态
|
||
|
||
**未启用**。`ssl_ctx.set_verify_mode` 从未调用,默认 `verify_none` 接受一切证书。hostname 验证(`SSL_set1_host` / `boost::asio::ssl::host_name_verification`)也未实现。`set_default_verify_paths` 的调用形同虚设。
|
||
|
||
**影响评估**: 生产不可用。任何可访问网络路径的中间人可解密全部通信,包括 API 密钥、对话内容。在公共 WiFi 或不可信网络环境下,这是可直接利用的凭证泄露漏洞。
|
||
|
||
---
|
||
|
||
## 整体评级: C
|
||
|
||
| 维度 | 评级 |
|
||
|------|------|
|
||
| 跨 DLL 堆纪律 | A |
|
||
| 异常安全 ABI | B |
|
||
| 字符串返回生命周期 | A |
|
||
| TLS 证书验证 | F |
|
||
| 超时 | C |
|
||
| 错误码语义 | B |
|
||
| 并发安全 | A |
|
||
| 资源清理 | A |
|
||
| 缓冲区限制 | A |
|
||
| **综合** | **C** |
|
||
|
||
**总评**: RAII、堆纪律、字符串生命周期、并发安全均高质量。但 TLS 证书验证完全禁用 (F) 是致命安全缺陷,DNS 无超时可无限 hang。两个问题 (TLS + DNS) 使该插件在任何生产环境中不可用。修复后预期可达 A 级。
|