Files
dstalk/agents/audits/W13.5-session-tools-audit.md
XiuChengWu 47082376ef
Some checks failed
CI / Determine matrix (push) Has been cancelled
CI / ${{ matrix.os }} / ${{ matrix.build_type }} (push) Has been cancelled
Wave 10: deep audits of 5 unaudited plugins, smoke regression set (W13.1-W13.6)
- 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>
2026-05-27 09:32:13 +08:00

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 调用。两个文件在日志维度上安全。