Wave 10: deep audits of 5 unaudited plugins, smoke regression set (W13.1-W13.6)
Some checks failed
CI / Determine matrix (push) Has been cancelled
CI / ${{ matrix.os }} / ${{ matrix.build_type }} (push) Has been cancelled

- 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>
This commit is contained in:
2026-05-27 09:32:13 +08:00
parent 58869abc15
commit 47082376ef
12 changed files with 1384 additions and 2 deletions

View File

@@ -32,5 +32,11 @@ performance_log:
- 综合评级: B (堆纪律A, ABI B, 内存B, 并发C)
审计报告写入 agents/audits/W11.1-context-audit.md
rating: completed
- date: 2026-05-27
event: "W13.4 深度审计 lsp_plugin.cpp (749行)"
detail: |
lsp_plugin 首次全面审计 (9 维度). 堆纪律/字符串返回均合规(A); 异常安全全线失败——7 vtable + reader_loop + handle_message + on_shutdown 零 try/catch(违反§8,F); 致命死锁 L526→L547(持mutex调g_lsp_impl_stop→再次lock,F); server→client request 静默丢弃(B); ServerCapabilities丢失/rootUri硬编码(C); fetch_add溢出UB(B). TOP3: (1)死锁[严重] (2)异常穿越ABI[严重] (3)request丢弃+error丢失[高]. 综合 C.
审计报告: agents/audits/W13.4-lsp-audit.md
rating: completed
current_groups: []
---

View File

@@ -23,5 +23,8 @@ performance_log:
- date: 2026-05-27
event: "W10.1: 设计协作状态机 + 验收清单 + 失败回退协议,追加 WORKFLOW.md §11§13"
rating: completed
- date: 2026-05-27
event: "W13.1: 深度审计 anthropic_plugin.cpp (497行)6个C ABI函数零try/catch (§8违反)response_body泄漏 + 全局指针竞态tool_use静默丢弃。综合评级C。报告写入 agents/audits/W13.1-anthropic-audit.md"
rating: completed
current_groups: []
---

View File

@@ -0,0 +1,256 @@
# W13.1 Anthropic Plugin Audit
**Auditor**: 杨帆 (architect-yang)
**Date**: 2026-05-27
**File**: plugins/anthropic/src/anthropic_plugin.cpp (497 行)
**Wave Coverage**: W2.2 (仅 secure_zero 追加,全文从未审计)
---
## 文件总览
| 函数 | 行号 | 类型 | 用途 |
|------|------|------|------|
| `secure_zero` | 34-37 | 静态辅助 | volatile 写零擦除敏感数据 |
| `extract_host_port` | 42-62 | 静态辅助 | URL 解析 host/port/target |
| `build_headers_json` | 67-73 | 静态辅助 | 构建 Anthropic headers JSON (含 api_key) |
| `build_request_json` | 78-124 | 静态辅助 | 构建 Messages API 请求体 |
| `parse_response` | 129-195 | 静态辅助 | 解析非流式 JSON 响应 |
| `parse_sse_data` | 203-238 | 静态辅助 | 解析 SSE data 行 (Anthropic 格式) |
| **`my_configure`** | **243-261** | **vtable** | AI 服务 configure |
| **`my_chat`** | **266-306** | **vtable** | 非流式聊天 |
| **`my_chat_stream`** | **348-428** | **vtable** | 流式聊天 (SSE) |
| `sse_line_callback` | 321-346 | 回调 (C fn ptr) | SSE 行回调,传给 HTTP 服务 |
| **`my_free_result`** | **433-439** | **vtable** | 释放 chat_result 字段 |
| **`on_init`** | **454-468** | **生命周期** | 插件初始化 |
| **`on_shutdown`** | **470-478** | **生命周期** | 插件销毁 |
| `dstalk_plugin_init` | 494-497 | **导出入口** | 返回插件描述符 |
**vtable 绑定**: `g_service` (L444-449) 绑定 configure/chat/chat_stream/free_result。
**全局状态**: `g_host` (L14), `g_http` (L15), `g_config` (L16), `g_cfg` (L29)。
---
## 1. 审计维度: §5 + §8 异常安全
**规则 (§8.1)**: 所有 C ABI 导出函数on_init/on_shutdown/on_event + vtable 函数指针)必须 try/catch 包裹。
### 发现 1.1 — [H] 6 个 C ABI 函数零 try/catch 保护
以下所有通过 vtable 或 `dstalk_plugin_info_t` 暴露给 host 的函数,内部使用 `std::string` / `boost::json` / STL但**无一包裹 try/catch**
| 函数 | 行号 | 使用的可抛类型 | 风险 |
|------|------|---------------|------|
| `my_configure` | L243-261 | `std::string::operator=` (L247-250) | `std::bad_alloc``std::terminate()` |
| `my_chat` | L266-306 | `std::string` x5 (L279-286) + `boost::json` (build 系列) | 同上 |
| `my_chat_stream` | L348-428 | `std::string` x4 (L361-368) + `boost::json` | 同上 |
| `on_init` | L454-468 | `g_host->log` 可变参数 + query_service | 低概率但可能 |
| `on_shutdown` | L470-478 | `std::string::clear()` (noexcept) + `g_host->log` | 极低概率 |
| `sse_line_callback` | L321-346 | `std::string line_str(line)` (L326) | `std::bad_alloc` 抛向调用方 (网络插件) |
**违反 §8.1 条目 1+2+4**: on_init/on_shutdown + vtable 函数指针 + register_service 注册的函数。
**代价**: 任何 OOM 或 STL 异常 → 穿越 C 函数指针边界 → `std::terminate()` → 进程崩溃。
W11.1 对 context_plugin 发现了完全相同的模式 (trim_impl)本插件问题范围更广6 个函数 vs 1 个)。
**对比 deepseek_plugin.cpp**: deepseek 同函数**同样零 try/catch**my_configure L238-256, my_chat L261-301, my_chat_stream L337-417, on_init L443-457, sse_line_callback L317-335说明此反模式是项目级共性问题。
### 发现 1.2 — [M] sse_line_callback 无保护但可能被网络插件兜底
L326 `std::string line_str(line)` 可抛 `std::bad_alloc`。回调通过 C 函数指针 `dstalk_stream_cb` 传入网络插件的 `post_stream`。根据 security-logging.md 对 network_plugin 的描述L280-282 有 `catch (std::exception& e)`),网络插件的 do_post_stream 可能兜底捕获。但依赖调用方的异常保护是脆弱假设——若网络插件变更或不同版本,此防护消失。
---
## 2. 审计维度: §9 字符串返回值生命周期
**规则 (§9)**: vtable 中返回 `const char*` 的函数必须符合模式 A拥有权转移host 堆分配 + 调用方 host->free或模式 B静态生命周期
### 发现 2.1 — [L] 临时 std::string + c_str() + strdup 的脆弱模式
L405-406:
```c
r.error = g_host->strdup(("HTTP " + std::to_string(status_code)).c_str());
```
临时 `std::string` 在完整表达式结束前有效,`strdup` 在其析构前完成复制。**当前安全**,但重构时若分离 `c_str()``strdup` 调用会导致悬垂指针。Deepseek 同位置 (L393-395) 使用相同模式。
### 合规确认
| 检查项 | 状态 | 证据 |
|--------|------|------|
| chat_result 字段全部经 strdup 分配 | PASS | L144, L146, L150, L168, L176, L179, L186, L191, L275, L296, L357, L396-397, L403, L405-406, L418, L424 |
| free_result 全部经 host->free 释放 | PASS | L436-438 |
| 无锁外 c_str() 返回 | PASS | 插件无 mutex/lock |
| 无栈上 buffer 返回 | PASS | 全部 strdup 复制 |
| 无 `std::string{...}.c_str()` 裸返回 | PASS | 临时对象均在 strdup 前存活 |
---
## 3. 审计维度: §2 + §3 跨 DLL 堆纪律
**规则 (§3.2)**: 严禁直接调用 malloc/free/strdup/new/delete 处理跨 DLL 边界数据。
### 发现 3.1 — [H] response_body 泄漏my_chat 错误路径未释放
L291-297 (`my_chat`):
```c
int ret = g_http->post_json(..., &response_body, &status_code);
if (ret != 0) {
r.error = g_host->strdup("http request failed");
return r; // ← response_body 未释放!
}
```
成功路径 (L302-304) 正确释放了 `response_body`,但 `ret != 0` 错误路径泄漏。
**对比**: `my_chat_stream` (L408, L414) 在**所有路径**正确释放 `response_body``my_chat` 应与 `my_chat_stream` 保持一致。
**deepseek_plugin.cpp 同位置** (L286-293) 有相同泄漏——此为项目级模式 bug。
### 合规确认
| 检查项 | 状态 | 证据 |
|--------|------|------|
| 无裸 malloc/free | PASS | 全文 0 处 |
| 无裸 strdup | PASS | 全文 0 处 |
| 无裸 new/delete | PASS | 全文 0 处 |
| 跨边界分配均经 host->strdup | PASS | 全部 chat_result 字段 |
| 跨边界释放均经 host->free | PASS | L302-304, L408, L414, L436-438 |
| 插件内部 std::string 不出边界 | PASS | 仅用于局部逻辑 |
---
## 4. 审计维度: §6 atomic 回调 + 线程安全
**规则 (§6.1)**: 诊断回调用 `std::atomic` + acquire/release。服务注册表/事件总线均有内部锁。
### 发现 4.1 — [H] 全局指针无同步保护 (g_host / g_http / g_config)
| 变量 | 写点 (on_shutdown) | 读点 (服务函数) | 同步机制 |
|------|-------------------|-----------------|----------|
| `g_host` | L477 `= nullptr` | L255, L274, L296, L357, L436 等 | **无** |
| `g_http` | L475 `= nullptr` | L274, L356 | **无** |
| `g_config` | L476 `= nullptr` | (未读取,死变量) | **无** |
**Race 场景**: 线程 T1 在 `my_chat()` L274 读取 `g_http`(非 null线程 T2 在 `on_shutdown()` L475 写入 `nullptr`。T1 随后 L291 调用 `g_http->post_json(...)`**空指针解引用**
**缓解因素**: host 应保证 shutdown 前无 in-flight 调用,但 ABI 未显式保证。与 W11.1 发现的 context_plugin Race A 问题同根。
### 发现 4.2 — [L] g_config 死变量
L16 声明 `g_config`L458 赋值L476 置 null。全文无任何读取点。不造成泄漏但暗示设计与实现脱节。
### 发现 4.3 — [PASS] 流式回调路径无需 atomic
`StreamContext` (L312-318) 在 `my_chat_stream` 栈上创建 (L370),回调 `sse_line_callback``post_stream` 同步调用期间使用,无多线程竞争。
---
## 5. 审计维度: api_key 安全
### 发现 5.1 — [PASS] on_shutdown 正确 secure_zero
L473-474:
```c
secure_zero(g_cfg.api_key.data(), g_cfg.api_key.size());
g_cfg.api_key.clear();
```
W2.2 修复已验证生效:先 volatile 零覆盖,再 clear 释放。`secure_zero` (L34-37) 使用 `volatile char*` 写入循环,是标准可移植的编译器优化防护手段。
### 发现 5.2 — [PASS] 日志不泄漏 api_key
W9.3 已审计确认:`my_configure` L255-258 的日志调用有意排除 `api_key`,仅输出 model/base_url/max_tokens/temperature。`build_headers_json` (L67-73) 构建的 headers JSON 仅通过内存传递至 HTTP 请求,不进入日志管道。
### 发现 5.3 — [L] 堆内存残留
`build_headers_json` 返回的 `std::string``x-api-key: sk-...`,在 `my_chat`/`my_chat_stream` 栈上存活至函数返回。RAII 析构释放内存但不擦除。攻击者读取进程堆可发现 api_key 残留。这是所有内存中处理凭证的固有限制非本插件特有secure_zero 仅覆盖 `g_cfg.api_key` 主副本。
---
## 6. 审计维度: 错误处理路径
### 发现 6.1 — [L] my_chat 对 post_json 错误仅返回通用消息
L291-297: `ret != 0` 时仅返回 "http request failed",未利用 `status_code` 区分超时/SSL/DNS 错误。`my_chat_stream` (L402-406) 有更细粒度的错误消息(区分 `status_code <= 0` 为 "transport error")。
### 发现 6.2 — [PASS] 空响应/空 body 不崩溃
- `parse_response` L136/L158: `body ? body : "{}"` 保护 null 输入
- L177-179: content 数组为空 → "empty response"
- `my_chat_stream` L416-418: `accumulated.empty() && !saw_data_line` → "no content received"
- `parse_sse_data` L234: catch(...) 静默忽略 JSON 解析失败
### 发现 6.3 — [PASS] HTTP 错误码正确传播
`parse_response` L134-154: 非 2xx 状态码提取 error.message 或退化为 "HTTP NNN"。
`my_chat_stream` L388-411: 同模式,附加 `status_code <= 0` 的传输错误处理。
---
## 7. 审计维度: 资源生命周期
### 发现 7.1 — [PASS] 流式中断资源正确释放
- `StreamContext` (L312-318) 栈分配RAII 保证析构
- `response_body``my_chat_stream` L408 + L414 双路径释放
- `user_cb` 返回 0 时L340`sse_line_callback` 返回 0`post_stream` 应据此停止——无孤儿连接
- 流结束后 `ctx.accumulated` 正确取出赋值 L424
### 发现 7.2 — [L] my_chat_stream 忽略 post_stream 返回值
L379-383: `int ret = g_http->post_stream(...)` 的结果未被检查。`my_chat` 检查了 `ret`L295`my_chat_stream` 仅在 status_code 上做判断。若 `ret != 0``status_code == 0`(连接完全失败),会落入 L402 `status_code <= 0` → "transport error" 分支——实际可工作,但不一致。
---
## 8. 补充发现
### 8.1 — [L] Anthropic 特有: tool_use 块被静默忽略
`parse_response` L163-173 仅提取 `type == "text"` 的 content block。Anthropic API 在 tool-use 场景下返回 `type == "tool_use"` 的块,其内容不会被提取到 `r.content``r.tool_calls_json` 始终被设为 `nullptr` (L153, L171, L182, L187, L193)。调用方期待 tool_calls 时将收到空 content + 空 tool_calls——静默数据丢失。不过 `dstalk_ai_service_t.chat` 签名设有 `tools_json` 入参但未在 Anthropic 请求体中设置(对比 deepseek L129 有 `root["tools"] = json::parse(tools_json)`),因此当前工具调用完全不可用。
### 8.2 — [L] system 消息合并到顶层字段
L92-97: Anthropic API 要求 system 为顶层字段(非 messages 数组元素),插件正确实现了此分离。但多段 system 消息用 `"\n\n"` 拼接 (L95),跨消息语义边界可能模糊。
---
## TOP 3 严重问题
### 发现 1 — [H] 6 个 C ABI 函数零 try/catch (§8 违反正面清单)
**行号**: L243 (my_configure), L266 (my_chat), L348 (my_chat_stream), L321 (sse_line_callback), L454 (on_init), L470 (on_shutdown)
**问题**: 全部使用 std::string/boost::json 但无异常保护。任何 std::bad_alloc → std::terminate()。
**修复**: 每个函数外层加 try { ... } catch (const std::exception& e) { g_host->log(ERROR, ...); return 错误码; } catch (...) { return 错误码; }
### 发现 2 — [H] response_body 泄漏 + 全局指针无同步
**行号**: L295-297 (泄漏), L14-L16 vs L475-L477 (竞态)
**问题**: (a) my_chat 在 ret!=0 时未释放 response_bodymy_chat_stream 正确);(b) g_host/g_http/g_config 无原子/互斥保护on_shutdown 与 服务函数并发可空指针解引用。
**修复**: (a) 在 L296 前加 `if (response_body) g_host->free(response_body);`(b) g_host/g_http 用 `std::atomic` 或文档化单线程约定。
### 发现 3 — [M] Anthropic tool_use 响应静默丢弃
**行号**: L129-195 (parse_response), L78-124 (build_request_json)
**问题**: (a) build_request_json 未将 tools_json 参数写入请求体(对比 deepseek L128-130(b) parse_response 仅提取 text blocktool_use 块被忽略tool_calls_json 始终 nullptr。用户调用工具时得到空响应无错误提示。
---
## 整体评级: C
| 维度 | 评级 | 理由 |
|------|------|------|
| §8 异常安全 | **F** | 6 个函数零 try/catch——mandatory 规则完全未满足 |
| §2/§3 跨 DLL 堆 | **A** | 无裸 malloc/free/strdup全部经 host API |
| §9 字符串生命周期 | **B+** | 全部 strdup 合规,一处临时+strdup 脆弱模式 |
| §6 线程安全 | **C** | 全局指针无同步,与 W11.1 同根问题 |
| api_key 安全 | **A** | secure_zero 生效,日志无泄漏 |
| 错误处理 | **C+** | 空响应不崩溃,但 response_body 泄漏+返回值忽略 |
| 资源生命周期 | **B+** | 流式路径 RAII 干净,仅 my_chat 泄漏点 |
| **综合** | **C** | 堆纪律意外干净,但异常安全零覆盖 + 全局指针竞态 + response_body 泄漏 将评级拉至 C |
**总评**: anthropic_plugin 在跨 DLL 堆纪律方面表现好(与 context_plugin 一致),说明编码者遵循了正确的内存管理模式。但 **§8 异常安全是强制性要求,当前零覆盖在最坏情况下直接导致进程崩溃**。response_body 泄漏和全局指针竞态进一步降低可靠性。tool_use 静默丢弃虽非安全/稳定性问题,但构成功能缺陷。评级 C建议在下一 Wave 优先修复发现 1 和发现 2。
---
## 审计元数据
- **审计标准**: plugin-abi.md v1.1 (含 §8 异常安全 + §9 字符串生命周期W12.6 追加)
- **参考审计风格**: agents/audits/W11.1-context-audit.md
- **安全日志参考**: docs/explanation/security-logging.md (W9.3)
- **对比文件**: plugins/deepseek/src/deepseek_plugin.cpp (仅参考,不审计)
- **不修改文件**: anthropic_plugin.cpp (审计只读)

View File

@@ -0,0 +1,232 @@
# W13.2 DeepSeek Plugin Audit
**Auditor**: 孙宇 (engineer-sun)
**Date**: 2026-05-27
**File**: plugins/deepseek/src/deepseek_plugin.cpp (486 行)
**Reference**: plugin-abi.md §8 §9; anthropic_plugin.cpp (对比); W9.3 security-logging.md
---
## 文件总览
| 属性 | 值 |
|------|-----|
| 总行数 | 486 |
| 函数数 | 15 (含 2 个生命周期 + 1 个导出入口) |
| C ABI 暴露入口 | 7 (on_init, on_shutdown, configure, chat, chat_stream, free_result, dstalk_plugin_init) |
| 依赖 | dstalk_host.h, dstalk_services.h, boost::json |
| 注册服务名 | "ai.deepseek" v1 |
---
## 维度 1: §5 + §8 异常安全 (C ABI 全部入口)
### 违反入口清单
所有通过 C ABI 暴露的函数均**未包裹 try/catch**:
| 函数 | 行号 | 可抛 C++ 操作 | 触发条件 |
|------|------|---------------|----------|
| `my_configure` | 238-256 | `std::string::operator=`, `g_host->log` | bad_alloc, log 内部异常 |
| `my_chat` | 261-301 | `std::string`, `extract_host_port`, `build_request_json``json::parse(tools_json)`, `append_history``json::parse(tool_calls_json)` | bad_alloc, 恶意/畸形 tools_json, 畸形 tool_calls_json |
| `my_chat_stream` | 337-417 | 同上 | 同上 |
| `on_init` | 443-457 | `query_service`, `register_service`, `log` | bad_alloc (低概率) |
| `on_shutdown` | 459-467 | `secure_zero`, `std::string::clear` | 实际不会抛异常 |
### 关键发现 1 — [严重] C++ 异常可穿越 ABI 边界导致 std::terminate()
- **行号**: L91 (`append_history`), L129 (`build_request_json`), L261-301 (`my_chat`), L337-417 (`my_chat_stream`)
- **触发路径**: `my_chat(history, ..., tools_json)``build_request_json` → L129 `json::parse(tools_json)` — 若 tools_json 格式错误,`json::parse` 抛出 `std::system_error`。该异常在无 try/catch 的 `my_chat` 中穿越 C ABI 边界 → `std::terminate()` → 进程崩溃。
- **同样路径**: L91 `json::parse(m.tool_calls_json)``append_history` 中 — 若历史消息包含畸形 tool_calls_json同样触发。
- **对比 anthropic**: anthropic 插件完全相同的漏洞 (W13.1 杨帆审计中),属结构性缺陷。
- **违反**: plugin-abi.md §8.2 "每个使用 C++ STL 类型的 ABI 函数外层必须包裹 try/catch"。
### 发现 2 — [中] parse_response 有 try/catch 但 build_request_json 无
- `parse_response` (L166-202) 内部有 `try/catch(std::exception&)/catch(...)`,自身安全。
- 但其调用方 `my_chat` (L261) 在调用 `build_request_json` (L278) 时无保护,而 `build_request_json` 在 L129 `json::parse(tools_json)` 可抛异常。
- **不对称保护**: 响应解析安全,请求构建不安全。
---
## 维度 2: §9 字符串返回值生命周期
**评级: A** — 无违规。
| 检查点 | 行号 | 方法 | 判定 |
|--------|------|------|------|
| `dstalk_chat_result_t.content` | L174, L413 | `g_host->strdup(content.c_str())` | 模式 A (拥有权转移) |
| `dstalk_chat_result_t.error` | L151, L155, L158, L187, L194 等 | `g_host->strdup(...)` | 模式 A |
| `dstalk_chat_result_t.tool_calls_json` | L178 | `g_host->strdup(tc.c_str())` | 模式 A |
| `my_free_result` | L422-428 | `g_host->free()` 逐个字段 | 正确释放 |
| `dstalk_plugin_info_t` name/version/description | L472-476 | 静态字符串字面量 | 模式 B (静态生命周期) |
- L151 临时 `std::string``.c_str()` 传给 `strdup`: 因临时对象在完整表达式结束时析构strdup 在此之前完成复制,安全。✅
- L291 `r.error = g_host->strdup("http request failed")` — 字面量 → 堆副本,正确。✅
---
## 维度 3: §2 跨 DLL 堆纪律
**评级: A** — 零违规。
| 搜索项 | 结果 |
|--------|------|
| 裸 `malloc` / `free` | 0 |
| 裸 `strdup` (非 host->) | 0 |
| 裸 `new` / `delete` | 0 |
| `g_host->alloc` / `g_host->free` / `g_host->strdup` | 所有跨边界分配均通过此路径 |
| `std::string` 跨边界 | 否 — 仅内部使用,不出 DLL |
内部 `std::string` (PluginConfig, extract_host_port, build_* 等) 均不穿越 DLL 边界。✅
---
## 维度 4: §6 原子回调 / 并发安全
**评级: C** — 与 W11.1 context_plugin 相同模式。
| 变量 | 写入点 | 读取点 | 同步 |
|------|--------|--------|------|
| `g_host` | L445 (on_init), L466 (on_shutdown) | L150, L155, L158, L250-253, L291, L298, L413, L425-427 | **无** |
| `g_http` | L446 (on_init), L464 (on_shutdown) | L269, L286, L345, L367 | **无** |
| `g_config` | L447 (on_init), L465 (on_shutdown) | 无读取 | **无** |
**Race**: `on_shutdown``g_host = nullptr` (L466) 与 `my_chat` 读取 `g_host` (L291) 无同步。若 host 在 shutdown 期间不保证无 in-flight 调用,则为数据竞争。虽与 codebase 其余部分一致§6.5 PluginLoader 无内部互斥),但仍是技术债务。
---
## 维度 5: SSE 解析鲁棒性 (核心专项)
### 发现 3 — [中] SSE [DONE] sentinel 精确匹配过脆
- **行号**: L208-216 (`parse_sse_line`)
- **代码**: `if (data == "[DONE]")` — 精确字符串比较。
- **问题**: 若 server 返回 `data: [DONE] ` (尾随空格) 或 `data: [DONE]` (双空格)sentinel 不匹配 → `parse_sse_line` 返回 false → `sse_line_callback` 返回 1 (continue) → 流永不终止。
- **缓解**: OpenAI/DeepSeek 官方实现始终使用精确 `data: [DONE]`,故此问题仅在第三方兼容 server 或未来协议变更时出现。
- **对比 anthropic**: anthropic 使用 `type == "message_stop"` JSON 字段检测,对空格变化免疫。
### SSE 解析鲁棒性逐项分析
| 场景 | 行号 | 行为 | 判定 |
|------|------|------|------|
| `data:` 未以 `"data: "` 开头 | L210 | `rfind` 返回非 0 → false, 行被跳过 | 安全 (line 回调继续) |
| JSON 解析失败 (畸形 JSON) | L229-231 | `catch(...)` 吞异常, 返回 false | 安全 ✅ |
| choices 数组为空 | L222 | `choices.empty()` → false, 行跳过 | 安全 ✅ |
| choices[0] 无 "delta" key | L223 | `obj["delta"]` 插入 null, `.as_object()` 抛异常 → catch | 安全 ✅ |
| delta 无 "content" key | L224 | `delta.contains("content")` → false | 安全 ✅ |
| data payload 为空 (line="data: ") | L212 | `substr(6)` → "", `== "[DONE]"` false, `json::parse("")` 抛异常 → catch | 安全 ✅ |
| 多字节 UTF-8 在行边界截断 | -- | 依赖 HTTP 层行缓冲; `std::string` 字节不可知, 不 crash | 低风险 |
| `[DONE]` 带尾随空格 | L213 | 精确匹配失败 → 流不终止 | **中风险** |
| server 发送非 SSE 行 (event:, id:, retry:) | L210 | `rfind("data: ", 0) != 0` → false, 跳过 | 安全 ✅ |
**综合评价**: `catch(...)` 提供了良好的兜底保护, 不会因畸形 server 输出 crash。唯一实质性缺陷是 `[DONE]` 检测脆弱性。
---
## 维度 6: JSON 字段缺失/类型错误容错
### 非流式 parse_response (L137-203)
| 场景 | 行号 | 行为 | 判定 |
|------|------|------|------|
| body 为 nullptr | L146, L167 | `body ? body : "{}"` → "{}" | 安全 ✅ |
| HTTP 非 2xx + body JSON 无 "error" key | L148 | `obj.contains("error")` → false, 走 L157 generic error | 安全 ✅ |
| choices 缺失或非数组 | L169 | `obj["choices"].as_array()`→ 抛异常 → catch | 安全 ✅ |
| choices 为空数组 | L170 | `choices.empty()` → "empty response" | 安全 ✅ |
| choices[0] 无 "message" | L171 | `.as_object()` 抛异常 → catch | 安全 ✅ |
| message 无 "content" | L173 | `value_to<std::string>` 抛异常 → catch | 安全 ✅ |
| error.message 缺失/非字符串 | L150-151 | `value_to<std::string>` 抛异常 → 外层 catch | 安全 ✅ |
**评级: A** — 双重 catch (std::exception& + ...) + 合理的 fallback 逻辑。
---
## 维度 7: api_key 安全
**评级: B+** — 基本安全,有改进空间。
| 检查点 | 行号 | 判定 |
|--------|------|------|
| 日志不输出 api_key | L250-253 | ✅ (W9.3 已确认) |
| build_headers_json 结果不进入日志 | L67-72 | ✅ |
| secure_zero 清理 api_key | L462 | ✅ (on_shutdown 调用) |
| 内存中明文存储 | L29 | ⚠️ 全局 std::string 整个会话周期明文存在; 仅 shutdown 时清零 |
| Authorization header 构造 | L70 | `"Bearer " + api_key` → 临时 string, 无额外泄漏 |
**改进方向**: 每次 HTTP 请求后立即 secure_zero 临时 buffer减少明文驻留时间。但此为深度防御非必须。
---
## 维度 8: 与 anthropic 代码重复度
### 重复度量化
| 类别 | deepseek 行数 | anthropic 对应 | 重复度 |
|------|--------------|----------------|--------|
| 头文件/namespace | 9 | 9 | 100% |
| 全局指针 (g_host/g_http/g_config) | 3 | 3 | 100% |
| PluginConfig + g_cfg | 9 | 9 | 100% |
| secure_zero | 4 | 4 | 100% |
| extract_host_port | 21 | 21 | 100% |
| configure | 19 | 19 | ~95% (仅 log tag 不同) |
| chat (主流程) | 41 | 41 | ~90% (endpoint path 不同) |
| StreamContext + sse_line_callback | 28 | 28 | ~85% |
| chat_stream (主流程) | 80 | 80 | ~85% |
| free_result | 7 | 7 | 100% |
| g_service vtable | 6 | 6 | 100% 结构 |
| on_init | 15 | 15 | ~95% (service name 不同) |
| on_shutdown | 9 | 9 | ~95% (log tag 不同) |
| g_info + dstalk_plugin_init | 19 | 19 | ~95% |
| **可重构行合计** | **~270** | | **~55%** |
### 唯一代码
deepseek 真正独有的代码 (~130 行):
- `build_headers_json` (L67-72): 接收裸 api_key 并组装 Bearer token
- `append_history` (L77-97): tool 消息处理 (tool_call_id, tool_calls_json)
- `build_request_json` (L102-133): OpenAI chat completions 格式
- `parse_response` (L137-203): choices[] 数组格式解析
- `parse_sse_line` (L208-233): OpenAI SSE delta 格式
### 结论
**重复度 ~55%**。以下组件可抽取为共享基类/模板:
1. `extract_host_port` (21 行, 100% 相同)
2. `secure_zero` (4 行, 100% 相同)
3. `PluginConfig` + `my_configure` 模式 (~28 行, 95% 相同)
4. `my_chat` / `my_chat_stream` 主流程骨架 (~120 行, 85% 相同)
5. `free_result` / `g_service` / `on_init` / `on_shutdown` / `g_info` / `dstalk_plugin_init` (~56 行, 95% 相同)
**可重构面**: ~230 行可提取为 `ai_plugin_base` 共享库, 各 AI 插件仅需实现 JSON 构建/解析 (~130 行)。
---
## TOP 3 严重问题
1. **[严重] L91, L129, L261-L301, L337-L417 — C++ 异常穿越 C ABI 边界 (§8)**
`my_chat`/`my_chat_stream` 调用 `json::parse(tools_json)``json::parse(tool_calls_json)` 无 try/catch。畸形 JSON → 异常沿 C 函数指针返回 host → `std::terminate()` → 进程崩溃。与 anthropic 同源缺陷。
2. **[中] L208-L216 — SSE [DONE] sentinel 精确匹配脆弱**
`data == "[DONE]"` 精确比较。尾随空格或格式变化 → sentinel 不识别 → 流不终止 → 调用方 hang。
3. **[中] L14-16, L459-L466 — g_host/g_http/g_config 无同步读写**
`on_shutdown` 与 service 函数并发访问同组全局指针, 无 atomic/mutex。虽有隐式时序假设, 但属正式数据竞争 (与 codebase 其余部分一致)。
---
## 整体评级
| 维度 | 评级 |
|------|------|
| §5+§8 异常安全 | **D** — 所有 ABI 入口无保护, 畸形输入可 crash 进程 |
| §9 字符串生命周期 | **A** — 全部模式 A/B, 零违规 |
| §2 跨 DLL 堆纪律 | **A** — 零违规 |
| §6 并发安全 | **C** — 与 codebase 一致的数据竞争 |
| SSE 解析鲁棒性 | **B** — catch(...) 兜底好, [DONE] 检测脆 |
| JSON 容错 | **A** — 双重 catch + 合理 fallback |
| api_key 安全 | **B+** — secure_zero 到位, 明文驻留可优化 |
| 与 anthropic 重复 | **D** (维护面) — ~55% 重复, ~230 行可抽取 |
| **综合** | **C+** |
**总评**: SSE 解析因为有 `catch(...)` 全面兜底, 比预期更鲁棒。核心风险在于**所有 ABI 入口函数无 try/catch** — 一旦传入畸形 tools_json 或 tool_calls_json, JSON 解析异常直接导致进程 `std::terminate()`。这是可稳定复现的 crash 路径, 非理论威胁。与 anthropic 的 ~55% 重复度表明存在显著"可重构面", 建议后续 Wave 考虑抽取 `ai_plugin_base` 共享层。

View File

@@ -0,0 +1,166 @@
# 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 级。

View File

@@ -0,0 +1,269 @@
# W13.4 LSP Plugin 深度审计
**Auditor**: 黄岭 (architect-huang)
**Date**: 2026-05-27
**File**: plugins/lsp/src/lsp_plugin.cpp (749 行)
**Wave Coverage**: W6.1 仅审计 reader_loop (L415-459),其余 700+ 行首次审计
---
## 0. 文件总览
749 行。模块分布:
- Process 子进程 (start/stop/write/read_line/read_bytes): ~220 行 (L54-274)
- Service dispatch 实现 (start/stop/open/close/diagnostics/hover/completion): ~280 行 (L461-698)
- JSON-RPC 框架 (frame_message/parse_content_length/send_request/send_notification/handle_message): ~110 行 (L299-413)
- reader_loop 状态机 (W6.1 已修): ~45 行 (L415-459)
- State/Lifecycle/Descriptor: ~50 行 (L276-297 + L700-749)
- Headers/Globals: ~44 行 (L1-53)
## 1. §5 + §8 异常安全——穿越 ABI 边界
状态: **严重违反** FAIL
vtable 全部 7 个函数均以 C 函数指针暴露给 host (`dstalk_lsp_service_t`),但底层 C++ 实现大量使用 `std::string` / `json::object` / `json::parse` / `std::unique_lock` 等可抛异常类型,**零 try/catch 保护**:
| 函数 | 行号 | 可抛异常操作 |
|------|------|-------------|
| `g_lsp_impl_start` | L467-537 | json::object, std::string, std::thread, std::unique_lock |
| `g_lsp_impl_stop` | L539-566 | json::object, std::unique_lock |
| `g_lsp_impl_open_document` | L568-584 | json::object |
| `g_lsp_impl_close_document` | L586-598 | json::object |
| `g_lsp_impl_get_diagnostics` | L600-612 | std::lock_guard, std::string |
| `g_lsp_impl_get_hover` | L614-655 | json::object, json::parse, std::unique_lock, std::string |
| `g_lsp_impl_get_completion` | L657-698 | 同上 |
此外 `reader_loop` (L419-459)、`handle_message` (L375-413)、`on_shutdown` (L724-730) 均无保护。
**违反代价**: OOM 或任何 `boost::json` 内部异常穿越 C 函数指针边界 → `std::terminate()` → 进程崩溃零恢复机会。plugin-abi §8 标记此为强制规则 (§8.1 明确覆盖 service vtable 函数指针),当前代码全线违规。
对比 W11.1: context_plugin 的 trim_impl (L114-226) 同样违规,但当时仅 1 个 vtable 函数受影响。lsp_plugin 受影响面是其 7 倍。
评级: **F**
---
## 2. §9 字符串返回值生命周期
状态: **合规** PASS
三个返回 `char** json_out` 的函数:
- `g_lsp_impl_get_diagnostics` (L600-612): `g_host->strdup(it->second.c_str())``g_host->strdup("[]")`,均在 mutex 锁内复制。Mode A (§9.2),调用方 `host->free` 释放。合规。
- `g_lsp_impl_get_hover` (L614-655): `g_host->strdup(json::serialize(resp["result"]).c_str())`temporary 在 strdup 复制后销毁,指针独立。合规。
- `g_lsp_impl_get_completion` (L657-698): 同 get_hover。合规。
`dstalk_plugin_info_t` 的 name/version/description 均为 static 字面量 → Mode B。合规。
评级: **A**
---
## 3. §2 跨 DLL 堆纪律
状态: **合规** PASS
逐行检查所有 malloc/free/strdup/new/delete:
| 模式 | 结果 |
|------|------|
| 裸 `malloc`/`free`/`new`/`delete` | 0 处 |
| 裸 `strdup` | 1 处 (L143, POSIX 子进程内, fork 后 exec 前, 不跨 DLL) |
| `g_host->strdup` | 4 处 (L607/609/653/696), 全部正确 |
| `g_host->alloc`/`g_host->free` | 0 处 |
唯一裸 `strdup` 位于 `fork()` 后的子进程 (L127-151),该进程随即调用 `execvp()` 替换地址空间,或 `_exit(127)` 退出。不涉及跨 DLL 边界。无合规风险。
评级: **A**
---
## 4. 进程生命周期
状态: **多个问题** WARN
### 4.1 Win32: TerminateProcess 无条件调用 (L174-176)
```cpp
WaitForSingleObject(hProcess, 2000); // 返回值被忽略
TerminateProcess(hProcess, 1); // 无条件调用
```
即使进程在 2 秒内正常退出,`TerminateProcess` 仍被调用。虽对已退出进程无害(返回 ERROR_ACCESS_DENIED但语义错误且日志缺失。
### 4.2 POSIX: SIGKILL 后 waitpid 可能永久阻塞 (L191-192)
```cpp
kill(pid, SIGKILL);
waitpid(pid, &status, 0); // 阻塞等待,无超时
```
若子进程处于 D 状态(不可中断睡眠),`waitpid(0)` 永不返回 → 调用线程永久挂起。
### 4.3 POSIX 子进程: strdup 未检查 null (L143-144)
`strdup(cmd)` 在 fork 后调用,若返回 NULL → `strtok(NULL, ...)` → 段错误。虽在 exec 前,但仍是未处理失败路径。
### 4.4 start() 中不必要调用 stop() (L69)
`Process::start` 开头调用 `stop()`,后者在 Win32 上有 2 秒 `WaitForSingleObject`。若首次启动,句柄均为 INVALID无实际操作。但语义不清`stop()` 应独立于 `start()` 调用。
评级: **C**
---
## 5. JSON-RPC 协议合规
状态: **部分缺失** WARN
### 5.1 Content-Length 解析 (L323-341)
W6.1 已修。大小写不敏感、空白跳过、异常安全。合规。
### 5.2 消息分发缺口 (L384-412)
当前仅处理两种消息:
- Response: `id && !method` (L384)
- Notification: `method && !id` (L391)
**缺失**: Server→Client Request (`id && method`) 被静默忽略。LSP 规范中 server 可发送:
- `window/showMessageRequest` (需客户端响应)
- `workspace/applyEdit` (需客户端响应)
- `client/registerCapability`
- `workspace/configuration`
server 请求得不到响应可能导致 LS 功能降级或阻塞。
### 5.3 Error 对象未处理 (L651, L694)
`get_hover` / `get_completion` 仅检查 `resp.contains("result")`,不检查 `"error"`。若 server 返回 `{"jsonrpc":"2.0","id":N,"error":{"code":-32601,"message":"..."}}`,函数返回 -1 但 **丢弃错误详情**。调用方无法区分超时和 server 错误。
### 5.4 通知/响应的 ID 匹配 (L386, L388)
`as_int64()``static_cast<int>`: 若 server 回传 >INT_MAX 的 id 值则截断。不过当前 id 由本端控制,仅 server→client request 场景可能触发。
评级: **B**
---
## 6. 状态机 / 线程同步
状态: **死锁 + 数据竞争** FAIL
### 6.1 致命死锁: start 超时路径 (L519-L526 → L547)
```
L519: std::unique_lock<std::mutex> lock(g_lsp.mutex); // 获取 mutex
L520: g_lsp.cv.wait_for(lock, 10s, ...); // 超时返回lock 仍持有
L526: g_lsp_impl_stop(); // → 进入 g_lsp_impl_stop
L547: std::unique_lock<std::mutex> lock(g_lsp.mutex); // 再次 lock 同一非递归 mutex → 死锁!
```
`std::mutex` 非递归,同线程二次 lock 为未定义行为Windows/Linux 均死锁。initialize 超时 (10s) 后触发,调用线程永久挂起。
注意: `get_hover`/`get_completion` 超时时仅返回 -1不调 stop无此问题。
### 6.2 写管道无同步 (L357, L368)
`send_request``send_notification` 均调用 `proc.write()` 而无 mutex 保护。若多线程并发调用 LSP 服务 → JSON 帧在管道上交错 → 协议解析失败。
### 6.3 读管道关闭的竞态 (L558-562 vs L228)
`g_lsp_impl_stop` 的时序: L558 `running=false` → L559 `proc.stop()` (关闭 hStdOut) → L562 `join`。reader 线程可能正阻塞于 `ReadFile(hStdOut)` (L228),句柄关闭触发 FALSE 返回 → `read_line` 返回 false → loop 退出 → join 成功。此竞态**被利用**来唤醒 reader属有意设计。可工作但脆弱。
评级: **F**
---
## 7. 请求 ID 管理
状态: **潜在 UB** WARN
- L284: `std::atomic<int> next_id{1};`
- L348: `int id = g_lsp.next_id.fetch_add(1);`
- L484: `g_lsp.next_id = 1;` (重启用)
问题:
1. **fetch_add 溢出**: `std::atomic<int>::fetch_add` 在有符号整型溢出时是未定义行为 (C++ 标准 [atomics.types.int]/8)。虽 2^31 次请求极难达到,但长生命期 LS 进程不排除。
2. **重启安全**: L484 重置为 1 时旧 pending 已在 L551 清除,安全。
3. **窄化转换**: L386 `as_int64()``int` 截断(见 §5.4)。
评级: **B**
---
## 8. 资源清理
状态: **基本正确** OK
`on_shutdown` (L724-730): 检查 running → 调 `g_lsp_impl_stop` → 置 `g_host=nullptr`。路径正确。
`g_lsp_impl_stop` (L539-566):
1. shutdown request + 2s wait
2. exit notification
3. `running = false`
4. `proc.stop()` (terminate/kill 子进程)
5. `reader_thread.join()`
6. `diagnostics.clear()`
时序正确。pending_responses 在 L551 清除后,任何在 `cv.wait_for` 阻塞的调用者因 `!g_lsp.running` 被唤醒并返回 -1。无泄漏。
`g_host``on_shutdown` 与 service 函数间无同步 (同 W11.1 的 Race A)。缓解: host 保证 shutdown 前无 in-flight 调用。
评级: **B**
---
## 9. 能力协商
状态: **不完整** WARN
- `initialize` 请求中声明 hover/completion/diagnostic 能力 (L491-503)。合规。
- `initialized` 通知在 initialize 响应后发送 (L533)。符合 LSP spec。
- **ServerCapabilities 被丢弃** (L529): initialize 响应仅取出后删除server 上报的能力集完全未被读取。若 server 不支持 hover插件不会知道仍会发送 hover 请求。
- **rootUri 硬编码 nullptr** (L511): 多数 LS 需要 rootUri 提供项目根目录以建立索引。缺少 rootUri 可导致 go-to-definition、workspace symbols 等完全不工作。
评级: **C**
---
## TOP 3 严重问题
### 1 — [严重] initialize 超时死锁 (L519-526 → L547)
`g_lsp_impl_start` 持有 `g_lsp.mutex` (L519) 时调用 `g_lsp_impl_stop()` (L526),后者在 L547 再次 `unique_lock` 同一非递归 mutex → **自死锁,线程永久挂起**。任何 initialize 超时场景server 10s 不响应)触发。
### 2 — [严重] 全 vtable 无异常保护,违反 §8 (L467-698 + L724-730)
7 个 service vtable 函数 + `reader_loop` + `handle_message` + `on_shutdown` 均大量使用 `std::string` / `json::object` / `json::parse`,零 `try/catch`。OOM 或 boost::json 异常穿越 C 函数指针边界 → `std::terminate()` 进程崩溃。受影响面是 context_plugin (W11.1) 的 7 倍。
### 3 — [高] Server→Client Request 被静默丢弃 + error 信息丢失 (L384-412 + L651/L694)
`handle_message` 仅分发 response/notification`id && method` 的 server request 直接丢弃不响应。server 可能因此阻塞(如 `window/showMessageRequest` 等待用户选择)。同时 error response 的 `error` 字段被忽略 (L651/L694),调用方无法区分超时与 server 错误。
---
## 整体评级
| 维度 | 评级 |
|------|------|
| §5+§8 异常安全 (ABI) | F (全 vtable 裸奔) |
| §9 字符串返回 | A (完全合规) |
| §2 跨 DLL 堆纪律 | A (0 处违规) |
| 进程生命周期 | C (死锁 + waitpid 阻塞 + strdup 未检查) |
| JSON-RPC 协议合规 | B (分发缺口 + error 丢弃) |
| 状态机 / 线程同步 | F (死锁 + pipe write 竞态) |
| 请求 ID 管理 | B (fetch_add 溢出 UB) |
| 资源清理 | B (路径正确,缺同步) |
| 能力协商 | C (ServerCapabilities 丢弃rootUri 硬编码 null) |
| **综合** | **C** |
**总评**: lsp_plugin 在堆纪律和字符串生命周期上完全合规(比 context_plugin 更干净),但 C++ 异常安全全线崩溃7 个 vtable + 多个内部函数无保护)。最致命的是 initialize 超时自死锁——触发条件明确且结果确定永久挂起。JSON-RPC 协议处理不完整 (server request 丢弃) 在真实 LS 场景下会显著降级功能。这些问题集中在 280 行 dispatch 代码中,修复范围可控(约 300 行需加 try/catch 包裹 + 修复死锁 + 补充分发逻辑)。
---
## 补充发现 (优先级低)
- **`#include <boost/json/src.hpp>` (L12)**: 将整个 Boost.JSON 实现编译进插件 DLL增加编译时间且可能与其他插件/库的 Boost.JSON 符号冲突。应改为仅 `#include <boost/json.hpp>`,通过 `target_link_libraries` 链接 boost::boost (CMakeLists.txt L16 已链接)。
- **trim (L303-311) 与 context_plugin 重复**: 相同逻辑出现于 context_plugin 的 trim两处独立维护。建议提取到 dstalk 核心库。
- **MultiByteToWideChar 缓冲区硬编码 4096 (L95)**: 命令行超长时静默截断,进程可能无法正确启动。
- **JSON-RPC "id" 同时接受 null**: LSP 规范允许 `"id": null` 表示通知。当前用 `msg.contains("id")` 仅检查 key 存在,未检查 null 值。若 server 误发 null-id response会被当作有效响应。暂非实际问题主流 LS 实现均守约)。
- **initialize 超时后 g_lsp_impl_stop 仍发 shutdown/exit 给可能未初始化的 LS**: 若 server 启动但未完成 initialize 握手,收到 shutdown 行为未定义。但在实践中,多数 LS 仍能处理。
- **Process::read_bytes L249 `buf.resize(count+1)``buf[count]='\0'`**: 刻意添加 null terminator`std::string` 不要求 null 终止,但后续 `json::parse(buf)` 需要。为性能优化(避免额外拷贝),可接受。

View File

@@ -0,0 +1,230 @@
# W13.5 Session + Tools Plugin 联合审计
**审计人**: 曹武 (security-cao)
**日期**: 2026-05-27
**文件**: plugins/session/src/session_plugin.cpp (264 行) + plugins/tools/src/tools_plugin.cpp (251 行)
**标尺**: plugin-abi.md §2 §5 §8 §9; security-logging.md
**前置工作**: W2.2 (api_key secure_zero, 仅 deepseek/anthropic) — 已注明合规; W9.3 (日志凭证审计, session 已审日志路径) — 已注明合规
---
## 1. Session 文件总览
session_plugin.cpp 实现 dstalk_session_service_t vtable (6 个函数): add / clear / save / load / history / token_count。
内部状态:
- `g_history`: std::vector<InternalMessage> — 完整消息历史 (role + content + tool_call_id + tool_calls_json)
- `g_cached_history`: std::vector<dstalk_message_t> — history() 返回的 C 数组缓存 (g_host->strdup 分配)
- `g_host` / `g_file_io`: 服务指针
依赖: file_io (save/load 持久化), Boost.JSON (序列化)。
---
## 2. Tools 文件总览
tools_plugin.cpp 实现 dstalk_tools_service_t vtable (4 个函数): register_tool / unregister_tool / get_tools_json / execute。
内置两个工具:
- `file_read`: 读取任意路径文件内容, 返回 JSON `{"content":"..."}`.
- `file_write`: 写入任意路径, 返回 JSON `{"success":true}`.
内部状态:
- `g_tools`: std::vector<ToolDef> — 已注册工具列表
- `g_host` / `g_file_io`: 服务指针
依赖: file_io, Boost.JSON。
---
## 3. 按维度发现
### 维度 1: Session 敏感数据持有与清零 [session]
**W2.2 覆盖范围**: api_key 的 secure_zero 仅实现在 deepseek_plugin.cpp 和 anthropic_plugin.cpp 的 anonymous namespace 内。session 插件**不在** W2.2 范围内 — 已注明合规。
**发现 1.1 — [高] 会话历史明文持有可能含 token 的数据** (session L33-40, L127-135)
- g_history 存储完整的 InternalMessage (含 tool_calls_json)。AI 模型回复中的 tool_calls 可包含 API 返回的敏感参数。
- on_shutdown (L242-249) 仅调用 g_history.clear() → std::string 析构 → 内存归还堆, **未 secure_zero 擦除**
- g_cached_history 中的字符串通过 g_host->free 释放 (L104-107), 同样未擦除。
- 与 W2.2 的 secure_zero 处置形成落差: api_key 被安全擦除, 但可能包含等效敏感度的会话数据未受同等保护。
**发现 1.2 — [中] 会话落盘明文无保护** (session L141-157)
- session_save 将完整消息 JSON 逐行写入 path 指定文件。content 字段包含用户输入 + AI 回复原文, tool_calls_json 包含工具调用参数。
- 无加密、无访问控制、无审计日志。
- path 参数无校验 — 依赖 file_io 服务实现 (若 file_io 无 sandbox, 可写任意路径)。
- 评级: 若 g_history 中包含 token 或隐私数据, 则构成明文存储敏感信息。
### 维度 2: Session 历史持久化安全 [session]
- 见维度 1 发现 1.2。补充: session_load (L159-202) 同样无路径校验, 可加载攻击者构造的 JSON 文件注入恶意历史记录 (如伪造 tool_call_id 导致下游误操作)。
- 加载时的 JSON 解析异常仅返回 -1 未记日志 (L194-195), 不利于入侵检测。
### 维度 3: Tools 路径校验与注入防护 [tools]
**发现 3.1 — [严重] 路径遍历: file_read / file_write 无路径消毒** (tools L50, L85)
- builtin_file_read (L39-67): `g_file_io->read(path.c_str(), &content)` — path 直接来自用户 JSON 输入, 未经任何规范化/白名单/拒绝遍历序列 (../) 检查。
- builtin_file_write (L69-98): 同。
- 攻击场景: `{"path":"../../../etc/passwd"}``{"path":"C:/Windows/System32/drivers/etc/hosts"}` — 取决于 file_io 底层实现。
- CVSS 初步评分: 7.5 (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N 需确认调用方可控性) — 若 AI 模型被 prompt injection 诱导调用 file_read/file_write, 攻击者可读写任意文件。
- **未发现命令注入**: 全文搜索无 system() / popen() / exec() / ShellExecute() / CreateProcess() 调用。仅通过 file_io 服务间接操作文件系统, 非 shell 执行。
### 维度 4: Tools dispatch 权限校验 [tools]
**发现 4.1 — [中] tools_execute 无调用方身份/权限检查** (tools L160-190)
- 任何持有 tools service vtable 指针的代码均可调用 execute(name, args) 执行任意已注册工具。
- 无 capabilities / ACL / rate limiting / audit logging。
- 如果未来注册 shell_exec 或 network 工具, 风险急剧升级。当前仅 file_read/write 但仍可被恶意插件滥用。
### 维度 5: §5 + §8 异常安全 (全部 C ABI 入口) [session + tools 共有]
**W11.1 审计已知模式**: context_plugin 因同样问题被评为 B 级 (trim_impl 无 try/catch)。
**发现 5.1 — [高] Session: 5/8 个 C ABI 函数无异常保护**
| 函数 | 行号 | 风险操作 | try/catch |
|------|------|----------|-----------|
| session_add | L127-135 | std::string 构造, vector::push_back | **无** |
| session_save | L141-157 | std::string, json::object, json::serialize | **无** |
| session_load | L159-202 | std::string, std::vector, string::substr | 仅 L178-196 (json::parse 部分) |
| session_history → rebuild_cached_history | L101-121 | std::vector, g_host->strdup | **无** |
| on_shutdown | L242-249 | → rebuild_cached_history | **无** |
| session_clear | L137 | g_history.clear() | noexcept — **安全** |
| session_token_count | L210 | 纯计算 | **低风险** |
| on_init | L227-240 | host->log, register_service | host 侧有保护, 插件侧**无** |
**发现 5.2 — [高] Tools: 5/8 个 C ABI 函数无异常保护**
| 函数 | 行号 | 风险操作 | try/catch |
|------|------|----------|-----------|
| tools_register_tool | L106-121 | std::string, vector::push_back | **无** |
| tools_unregister_tool | L123-130 | std::string, vector::erase | **无** |
| tools_get_tools_json | L132-157 | json::parse, json::serialize, std::string | **无** |
| on_init | L203-230 | → tools_register_tool x2 | **间接暴露** |
| tools_execute | L160-190 | handler 调用 | **部分** (L180-189) |
| builtin_file_read | L39-67 | json::parse, std::string | **有** (L63-66) |
| builtin_file_write | L69-98 | json::parse, std::string | **有** (L94-97) |
| on_shutdown | L232-236 | clear + nullptr | noexcept — **安全** |
**违反条款**: plugin-abi §8.1 (适用范围第2项: service vtable 函数) + §8.2 (实施要求)。
**后果**: OOM 或 json::parse 异常时 → std::terminate() → 进程崩溃, 无恢复机会。
**发现 5.3 — [低] 异常消息信息泄露** (tools L64, L95)
- builtin_file_read/write 的 catch 块将 `e.what()` 直接拼入返回给调用方的 JSON 字符串。
- `e.what()` 可包含文件路径、系统错误描述等内部信息。
- 虽算不上安全漏洞 (调用方本就控制 path 参数), 但违反最小信息暴露原则。
### 维度 6: §9 字符串返回 [session + tools 共有]
**发现 6.1 — [中] session_history 返回指针在下一次调用时悬垂** (session L101-121, L204-208)
- `session_history()` 调用 `rebuild_cached_history()`, 后者先 `g_host->free` 所有旧 g_cached_history 字符串 (L104-107) 再重建。
- 调用方若持有上一次 history() 返回的 `const dstalk_message_t*`, 在下一次 history() 调用后其中的 role/content 指针已悬垂 (已 free)。
- 模式类似 dstalk_chat_result_t (§2.1), 但 **无文档警告** "must copy before next history()"。
- 违反 §9.2 模式 A 文档要求 ("caller must free" 等效说明缺失)。
**发现 6.2 — [低] Tools 返回字符串模式正确但缺文档** (tools L62, L93, L157)
- builtin_file_read/write, tools_get_tools_json, tools_execute 错误路径全部使用 g_host->strdup 返回 — Mode A, 正确。
- 但函数无注释说明 "caller must free with host->free", 违反 §9.2 文档要求。
### 维度 7: §2 跨 DLL 堆纪律 [session + tools 共有]
**两个文件均合规** ✅ (同 W11.1 审计结论 — 编码者即使未读 plugin-abi 也遵循了正确模式):
- 所有跨边界分配: g_host->strdup (session L115-118; tools L62, L93, L157)
- 所有跨边界释放: g_host->free (session L104-107, L167)
- 内部类型: std::string / std::vector 仅插件 DLL 内使用, 不出边界
- 无裸 malloc / free / strdup / new / delete 处理 host 传入/传出数据
**W2.1**: 两个文件均未搜到裸 free 调用。任何 W2.1 的 free 迁移已在上层重构中消化 (旧 deepseek_api.cpp 等已删除)。已注明合规。
### 维度 8: 并发安全 [session + tools 共有]
**发现 8.1 — [严重] 两个插件所有全局状态均无同步保护**
Session 竞态表:
| 变量 | 写入方 | 读取方 | 同步 |
|------|--------|--------|------|
| g_history | add, clear, load | save, token_count, rebuild_cached_history | **无** |
| g_cached_history | rebuild_cached_history (via history) | history | **无** |
| g_host | on_init, on_shutdown | rebuild_cached_history, save, load | **无** |
| g_file_io | on_init, on_shutdown | save, load | **无** |
Tools 竞态表:
| 变量 | 写入方 | 读取方 | 同步 |
|------|--------|--------|------|
| g_tools | register_tool, unregister_tool | get_tools_json, execute | **无** |
| g_host | on_init, on_shutdown | get_tools_json, execute, file_read, file_write | **无** |
| g_file_io | on_init, on_shutdown | file_read, file_write | **无** |
**典型竞态场景**:
- Race A (session): T1 add() push_back to g_history, T2 save() iterates → 迭代器失效 / 数据损坏。
- Race B (session): T1 history() reads g_cached_history, T2 history() frees + rebuilds → UAF。
- Race C (tools): T1 execute() iterates g_tools, T2 unregister_tool() erases → 迭代器失效。
- Race D (共有): T1 any-service reads g_host/g_file_io, T2 on_shutdown() writes nullptr → NPD (空指针解引用)。
Race D 已于 W11.1 审计确认 (context 的同模式问题评为 C 级)。session + tools 的竞态面更大 (更多全局可变状态)。
---
## 4. TOP 3 严重问题
### TOP 1 — [严重] 路径遍历: file_read/file_write 无路径消毒 (tools L50, L85)
- 攻击者控制 args_json → 读写任意文件系统路径。无 sandbox、无白名单、无规范化。
- 若 AI 模型被 prompt injection 诱导调用工具, 可泄露系统配置/源码/凭证文件。
- CVSS: 7.5 (High) — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N (假设调用方可控)
- 修复方向: 在 builtin_file_read/write 中添加路径规范化 (realpath/Canonicalize), 限制在预设工作目录内, 拒绝 ".." 段。
### TOP 2 — [严重] 全局状态无同步 → 多线程竞态 (session + tools, 全文)
- g_history / g_tools / g_host / g_file_io 读写均无 mutex。
- 后果: 迭代器失效 (crash)、use-after-free (crash/任意代码)、空指针解引用 (crash)。
- 影响面: 两个插件共 8 个 vtable 函数 + 2 个 on_shutdown = 10 个竞态入口。
- CVSS: 7.5 (High) — 多线程使用时 crash 概率高, 可能远程触发 (通过 AI chat 间接调用 session/tools)。
- 修复方向: 每个插件引入 std::shared_mutex (或 std::mutex), 写操作持 unique_lock, 读操作持 shared_lock。on_shutdown 需额外原子/锁确保服务调用排空。
### TOP 3 — [高] C++ 异常穿越 ABI 边界 (session L127/L141/L204/L242 + tools L106/L132/L203)
- session 5 个函数 + tools 4 个函数使用 std::string/std::vector/json::parse 但无 try/catch 包裹。
- 违反 plugin-abi §8 硬性规则 → OOM 或解析异常时 std::terminate() → 进程崩溃。
- W11.1 已在 context 中识别同模式问题 (评为 B 级, trim_impl 无保护)。session/tools 影响面更广 (9 个 vtable 函数 vs context 的 3 个)。
- CVSS: 5.9 (Medium) — 触发需 OOM 或恶意 JSON 输入, 后果为 DoS (进程崩溃)。
- 修复方向: 按 §8.2 标准模式, 每个使用 C++ 类型的 C ABI 函数体包裹 `try { ... } catch (const std::exception& e) { log; return -1/nullptr; } catch (...) { log; return -1/nullptr; }`
---
## 5. 关键问题回答
| 问题 | 结论 |
|------|------|
| 是否发现凭证泄露? | **潜在风险**: session_save 明文落盘含 tool_calls_json, 若工具调用参数含 token 则泄露。当前未确认可利用实例, 但防御缺失。 |
| 是否发现命令注入? | **未发现**: 两文件均无 system()/popen()/exec() 调用。tools 仅通过 file_io 服务间接操作文件, 非 shell 执行。 |
| 是否发现路径遍历? | **确认**: tools builtin_file_read/write 无路径消毒 (L50, L85), 攻击者可读写任意文件。 |
---
## 6. 整体评级
### Session: D+ (62/100)
| 维度 | 评级 | 说明 |
|------|------|------|
| §2 跨 DLL 堆 | A | 完全合规, host->strdup/free |
| §5/§8 异常安全 | D | 5/8 C ABI 函数无 try/catch |
| §9 字符串返回 | C | 模式正确但缺文档; history() 悬垂风险 |
| 敏感数据处理 | C | 明文落盘 + 无 secure_zero |
| 并发安全 | D | 零同步, 4 个静态全局无保护 |
| **综合** | **D+** | 堆纪律干净但异常安全/并发/数据保护三项不及格 |
### Tools: D (60/100)
| 维度 | 评级 | 说明 |
|------|------|------|
| §2 跨 DLL 堆 | A | 完全合规, host->strdup/free |
| §5/§8 异常安全 | C | 内置工具有保护, 注册/查询/on_init 无 |
| §9 字符串返回 | B | Mode A 正确, 缺文档 |
| 路径校验 | F | file_read/write 任意路径, 无消毒 |
| 访问控制 | F | execute 无调用方身份/权限检查 |
| 并发安全 | D | 零同步, 3 个静态全局无保护 |
| **综合** | **D** | 路径遍历为硬伤; 并发/异常安全与 session 同病 |
---
## 7. 低优先级发现
- **session_load 异常吞没无日志** (session L194-195): 解析失败仅返回 -1 不记日志。
- **tools 异常消息泄露** (tools L64, L95): e.what() 返回给调用方, 含内部路径/系统错误。
- **两个插件的 on_init 均未检查 register_service 返回值**: 若服务名冲突返回 -2 被静默忽略。
- **W9.3 已审结论维持**: session 仅 on_init L233 调用 host->log(ERROR, 静态字符串) — 无凭证日志泄露。tools 无任何 host->log 调用。两个文件在日志维度上安全。

View File

@@ -30,4 +30,12 @@ performance_log:
修复后:正确遍历所有 header 行,空行后若仍未找到 Content-Length 则记录错误并跳过帧。
编译通过smoke test 通过。
current_groups: []
- date: 2026-05-27
event: "W13.2: 深度审计 deepseek_plugin.cpp (486 行) — SSE 解析/ABI 异常安全/堆纪律/重复度"
rating: completed
details: |
SSE 解析: catch(...) 全面兜底,不 crash[DONE] sentinel 精确匹配脆弱。
核心发现7 个 C ABI 入口均无 try/catch畸形 tools_json → json::parse 异常 → std::terminate()。
跨 DLL 堆/字符串生命周期 A 级合规;与 anthropic ~55% 重复,~230 行可抽取为 ai_plugin_base。
综合评级 C+。报告写入 agents/audits/W13.2-deepseek-audit.md。
---

View File

@@ -33,6 +33,9 @@ performance_log:
- date: 2026-05-27
event: "W7: smoke test 插件加载修复。tests/CMakeLists.txt 增加 POST_BUILD 拷贝逻辑修复5个插件 Boost.JSON header-only 链接,实现 9/9 插件加载,所有 [Block] 零 WARN100% pass"
rating: A+
- date: 2026-05-27
event: "W13.3: network_plugin.cpp 深度审计 (322行, 9维度)。发现 TLS 证书验证完全禁用 (F, CVSS 7.4) + DNS 解析无超时 (永久hang) + 缺 catch(...)。RAII/堆纪律/并发 A 级。综合 C 级"
rating: A
current_groups:
- grp-quality-core (组长)
---

View File

@@ -30,5 +30,8 @@ performance_log:
- date: 2026-05-27
event: "W11.7 破坏性输入测试build/dstalk-cli/dstalk-cli.exe (commit 004a81d) 10 场景全 PASS 零崩溃。发现 BUG-1 [CRITICAL] build/bin/ 下存在损坏副本 (MD5 d8e8c92b vs 正常 803ca2ea命令解析全失效)BUG-2 /clear 谎报成功BUG-3 /context 静默无输出BUG-4 /file write 裸命令匹配失败。报告写入 agents/audits/W11.7-destructive-test.md"
rating: completed
- date: 2026-05-27
event: "W13.6 扩展 tests/smoke_test.cpp (430→623 行, +193): 新增 4 个回归保护 case — R1 context max_tokens trim (覆盖 W11.1 Discovery 3 死变量 + W11.7 BUG-3 /context 静默), R2 config 双 store 一致性 (host config_set → plugin service get, 发现跨 store 可见性仍为已知 gap 以 WARN 记录), R3 HTTP 错误路径 (127.0.0.1:1 连接拒绝返回 error 不崩溃), R4 重复 init/shutdown 生命周期 (3 轮循环全部通过). cmake build 0 error, ctest 4/4 pass."
rating: completed
current_groups: []
---

View File

@@ -37,5 +37,18 @@ performance_log:
build_headers_json() 产生的凭证字符串仅通过内存传递给 Beast HTTP未经过日志管道。
低风险/假阳性 2 项lsp server_cmd 日志 + network e.what() 异常信息),无需代码修改。
审计报告写入 docs/explanation/security-logging.md。CVSS: N/A无可利用漏洞
- date: 2026-05-27
event: "W13.5: session + tools 联合安全审计 (515行)"
rating: done
detail: |
联合审计 session_plugin.cpp (264行) + tools_plugin.cpp (251行)。
TOP3: (1) tools L50/L85 路径遍历→任意文件读写 (CVSS 7.5);
(2) 两插件全文 static global 无 mutex→多线程竞态 UAF/NPD;
(3) session L127/L141/L204/L242 + tools L106/L132/L203
缺 try/catch→§8违反→std::terminate。
凭证泄露: session_save 明文落盘含 tool_calls_json(潜在token泄露)。
命令注入: 未发现。路径遍历: tools 确认。
评级 session:D+ / tools:D。
报告: agents/audits/W13.5-session-tools-audit.md
current_groups: []
---