- W16.1 (曹武): F-11.7-1 CLOSED — confirmed W12.4 fix, corrupt binary eliminated - W16.2 (孙宇): F-11.1-1 FIXED — context_plugin.cpp try/catch on set_max_tokens + on_shutdown - W16.3 (陈风): F-11.1-2 CLOSED — confirmed W12.1 fix, strdup OOM protection already in place - W16.4 (胡桐): Integrate check_agents_metadata into refresh_status.py as pre-gate (error→exit 1) - W16.5 (周岩): Add Findings Summary to W13.3 network audit, register 3 findings - W16.6 (赵码): Add Findings Summary to W13.1+W13.2 AI audits, register 8 findings (4 already W14-fixed) Build 0 error, ctest 4/4 pass, metadata check 0 error 0 warning. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
9.0 KiB
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 (完全未启用)
// 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): 栈上对象,内含 RAIIio_context+ssl::context。析构自动清理。ssl_stream(L144),resolver(L141),flat_buffer(L145): 全部栈上 RAII。- 早期退出路径:
goto done(L150, L214) — 栈展开正确析构所有对象。 - 异常路径:
catch(std::exception&)(L251) — 栈展开触发析构。 - L249
cancel()+ L250stream.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 级。
Findings Summary
| ID | Severity | Title |
|---|---|---|
| F-13.3-1 | CRITICAL | TLS 证书验证完全禁用:set_verify_mode(ssl::verify_peer) 未调用,默认 verify_none 接受任何证书,无 hostname 验证 (L87-93) |
| F-13.3-2 | HIGH | DNS 解析无超时:resolver.resolve(host, port) 同步调用,socket 未创建无法设超时,DNS 无响应则线程永久阻塞 (L142) |
| F-13.3-3 | MEDIUM | 异常处理缺 catch(...) 兜底:仅捕获 std::exception&,非标准异常 (SEH/自定义) 穿越 C ABI → std::terminate() (L251) |