- 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>
14 KiB
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 — 完整消息历史 (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 — 已注册工具列表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 调用。两个文件在日志维度上安全。