会话记录 2026-04-05 — Web UI 安全与架构修复
时间:2026-04-05 凌晨
版本:v0.15.0(基于8d5d21e)
主题:通过对话记录审计发现并修复 Web UI 的安全隐患和架构缺陷
一、发现过程
灵通老师要求查看 Web UI 的对话记录。通过直接查询 chat_messages 表(145条记录),在对话内容中发现了多个异常行为模式,进而追溯代码发现了深层的架构问题。
对话记录中的异常
| 时间段 | 异常行为 | 消息ID |
|---|---|---|
| 01:17-01:19 | 灵依凭空编造"明天下午开会",用户反复追问后才承认日程中不存在 | 124-131 |
| 01:23-01:45 | 用户要求搜索AI伦理进展,搜索工具连续失败,灵依却说"我无法访问互联网" | 134-145 |
| 00:47-00:50 | 对话中灵依行为正常,但底层代码存在会话泄露风险 | 108-118 |
二、发现的问题及根因分析
2.1 API Key 明文硬编码 — 安全问题
严重程度:高
DashScope API Key 直接写在 4 个源文件的代码中:
| 文件 | 写法 | 严重性 |
|---|---|---|
web_app.py:23 |
纯硬编码,无环境变量 fallback | 最高 |
agent.py:21-23 |
os.environ.get(..., "sk-87b6...") |
高 |
web.py:14-16 |
os.environ.get(..., "sk-87b6...") |
高 |
voicecall.py:284-286 |
os.environ.get(..., "sk-87b6...") |
高 |
web_app.py 最为严重——完全跳过环境变量,直接硬编码。其他 3 个文件虽然读了环境变量,但把 key 作为默认值写在了代码里,推到 GitHub 会直接泄露。
2.2 对话状态全局共享 — 隐私/架构问题
严重程度:高
_conversation 是进程级全局 list,所有 WebSocket 客户端共享同一段对话上下文。
后果:
- 用户 A 发的消息会被包含在用户 B 的 LLM 上下文里(_conversation[-20:])
- 多个客户端连接时,互相能看到对方的对话历史(history 推送)
- 单用户场景下暂时不严重,但多设备同时连接时会出现混乱
2.3 对话列表无限增长 — 资源问题
严重程度:中
_conversation 只追加不清理。启动时加载最近 40 条,之后只增不减。虽然发给 LLM 时只取后 20 条(_conversation[-20:]),但内存中的列表持续膨胀,长时间运行后会占用大量内存。
2.4 LLM 幻觉 — 模型层面问题(未修复)
严重程度:中(对话体验影响大,但无代码漏洞)
灵依在消息 124-131 中凭空编造了"明天下午开会"的安排。用户追问三次才勉强承认。
根因:系统提示词(agent.py:481-486)已有"绝对不许编造"规则,但 qwen-turbo 模型仍会在缺少数据时自行补全。这不是代码 bug,是模型能力边界。更强的模型(如 qwen-plus)或更严格的 prompt 约束可能改善。
2.5 搜索工具失败 — 工具链路问题(未修复)
严重程度:中
用户要求搜索AI伦理进展(消息134),连续4次失败。灵依在失败后回答"我无法直接访问互联网"(消息137),但实际上 search_web 工具是存在的。
根因:search_web 通过抓取 DuckDuckGo HTML 实现(tools.py:369-391),可能被反爬机制拦截或网络不通。更深层的问题是 LLM 在工具调用失败后,没有正确回退为"搜索工具暂时不可用",反而否定了自身的能力——说明 LLM 对自己的工具集理解不够。
三、修复内容
已修复(4项)
| 修改 | 涉及文件 | 具体改动 |
|---|---|---|
| API Key 安全 | web_app.py, agent.py, web.py, voicecall.py |
删除硬编码 key,统一改为 os.environ.get("DASHSCOPE_API_KEY", "") |
| 对话隔离 | web_app.py |
_conversation 全局变量取消;每个 WS 连接创建独立的 local_conv;bridge 连接使用独立的 _bridge_conv |
| 对话限长 | web_app.py |
新增 _MAX_CONVERSATION = 60,每次对话后检查并截断 |
| LLM 接口改造 | web_app.py |
_chat_llm_with_context() 和 _smart_reply() 新增 conv 参数,支持传入不同的对话上下文 |
变更文件统计
src/lingyi/agent.py | 4 +-
src/lingyi/voicecall.py | 4 +-
src/lingyi/web.py | 4 +-
src/lingyi/web_app.py | 50 +++++++++++++++++++++----------
4 files changed, 40 insertions(+), 22 deletions(-)
部署注意
API Key 不再硬编码,启动前必须设置环境变量:
测试结果
251 passed, 1 deselected(跳过的 test_today_schedules 是预存在的周日运行失败,与本次修改无关)。
四、未修复项与后续建议
| 优先级 | 项目 | 说明 |
|---|---|---|
| P1 | LLM 幻觉 | 需要更强的模型或更严格的 prompt 约束,属于调参范畴而非代码修复 |
| P1 | search_web 工具健壮性 |
DuckDuckGo HTML 抓取不可靠,考虑备用搜索引擎或 API 调用方式 |
| P2 | web_app.py 中的 ruff warnings |
4 个 unused import(datetime)、1 个 unused variable(disc_id)、1 个空 f-string |
| P3 | 预存测试失败 test_today_schedules |
周日运行时 assert 失败,测试应 mock date.today() 而非依赖实际星期 |
五、经验教训
- 对话记录是最好的审计材料 — 通过查看真实对话,发现了代码审查容易忽略的运行时问题(幻觉、工具失败)
- 安全不能靠约定 — API Key 硬编码在 4 个文件里,说明没有统一的密钥管理策略。今后应考虑集中的配置管理
- 全局状态是隐患 —
_conversation全局共享在设计时可能是"简单够用",但在多连接场景下是严重缺陷 - 模型能力 ≠ 工具能力 — LLM 拥有
search_web工具却不"知道"自己能用,导致失败时给出错误解释。系统提示词需要更明确地列出可用工具