给自己的 AI 家教做了一次安全审计——6 个 P0/P1 漏洞的发现与修复实录
我给自己的 AI 家教系统(灵犀学堂)做了一次全面审计,结果发现:API 密钥明文裸奔、付费内容可免费白嫖、3 个用户共用同一个激活码、23 个临时脚本堆在根目录。这篇记录我怎么一步步发现问题、定位根因、逐个修复——以及为什么 AI 产品的安全审计比传统软件更难做。
1. 缘起:你的 AI 产品真的安全吗?
做 AI 产品的人很容易陷入一个误区:功能跑通了 = 产品没问题。
我的灵犀学堂(Education Agent)上线两个月,学生端能聊天、能做题、能看教案,遗忘曲线引擎每天自动提醒复习——看起来一切正常。直到 8 月 16 号,我决定给自己做一次全面审计。
为什么突然想到要审计?因为我在清理项目根目录时,发现了一堆一次性的 Python 脚本——fix_01a.py、cleanup_all_misplaced_objectives.py、batch_review_all.py……这些脚本是之前调试时临时写的,一直堆在那里没人管。我开始好奇:如果连脚本都没清理,其他地方是不是也有隐患?
于是我写了一个审计计划,从 6 个维度对整个系统做了一次体检。结果——每一个维度都发现了问题。
2. 审计发现:6 个维度,没有一个满分
2.1 安全与合规:密钥明文裸奔
第一个让我冒冷汗的发现:
1 | embedding_config.json → 完整明文 OpenRouter API key,文件权限 644(全局可读) |
644 权限意味着这台机器上的任何用户、任何进程都能读到这个密钥。 而这个密钥关联的是 OpenRouter 的付费账号——如果被人拿去调用,账单会直接飙升。
修复很简单:把密钥迁到环境变量,配置文件只保留引用。
1 | # 修复前:直接读配置文件里的明文密钥 |
一个 commit(ade8631),10 行代码,密钥从此不再落盘。
2.2 付费墙:形同虚设
这是最严重的业务漏洞。
灵犀学堂的前端 /lesson 页面有付费墙——没开通的用户看不到付费内容。但 API 层没有任何校验。
这意味着什么?只要知道 API 端点,任何人都可以绕过前端付费墙,直接拉取付费内容:
| 端点 | 漏洞 | 影响 |
|---|---|---|
GET /api/graph/{id} |
返回全部节点(含付费) | 知识点结构泄露 |
GET /api/question-bank/{id} |
返回付费题目+答案+解析 | 题库白嫖 |
POST /api/question-bank/check |
qid 可枚举 | 正确答案遍历 |
GET /api/learning/stage/{id} |
返回学习进度 | 数据泄露 |
POST /api/learning/advance |
可推进付费节点 | 无付费触发教学内容生成 |
5 个端点,全部裸奔。
修复方案:在每个端点加 has_access 校验——没开通对应商品的用户直接返回 403。核心逻辑复用已有的 RedeemStore.has_access(student_id, kp_id),免费节点放行,付费节点需开通任一包含该节点的商品。
1 | # 修复:每个内容端点加校验 |
一个 commit(7a68530),27 行代码,付费墙从前端延伸到 API 层。
教训:前端付费墙只是 UI 层的遮羞布。真正的安全边界在 API 层。
2.3 商业化:3 个用户共用一个激活码
审计激活码系统时发现了一个诡异的数据:
1 | 激活码 LX-JTXJ-4JLW-85VP → 3 个不同用户已兑换 |
3 个人买了同一个码?不对。这是一个部分写入回滚失败的 bug。
根因:RedeemStore.redeploy() 用 and 短路分别写两个文件——先写 redeem_codes.json(标记为 used),再写 entitlements.json(记录开通)。如果 codes 写成功但 ents 写失败,函数返回失败,但 codes 已经落盘为 used。而 ents 没写入,所以权益不存在。
但更诡异的是:后续重试时,因为 codes 已经是 used,又被拒绝——导致这个码既被标记为已使用,又没有对应的权益。而其他用户拿到同一个码(可能从二手渠道),发现它还是 unused(因为之前的失败回滚逻辑把 codes 恢复了),于是又能兑换。
修复:改为分别保存 + 失败回滚。任一文件写失败,恢复内存态和已落盘文件。
1 | # 修复前(and 短路,部分失败不回滚) |
2.4 垃圾教案:n0/n1/n2 幽灵
审计 review_progress.json 时发现 reviewed 列表末尾有 3 个条目:n0、n1、n2——它们不在 58 个正式知识点中,也不在 generated 或 extra_on_disk 列表中。
追踪发现,这是 8 月 7 号和 13 号两次调试时产生的测试残留教案。它们的 id 是 n0、n1、n2——调试用的占位节点名,不是真实知识点。
更麻烦的是:这些幽灵教案的 .html、.blocks.json、.sha256 三件套齐全,而且 review_progress.json 里还有它们的评审记录(score 67.6 / 60.0 / 78.2)。如果不清理,每次质量循环跑起来都会”看到”这些假数据。
根因:a2a_bridge 在图谱查不到节点时,会用空的 KnowledgeNode 兜底生成教案。n0/n1/n2 作为无效节点名,竟然也能通过生成管线。
修复:两道防线。第一道在 a2a_bridge,图谱查不到节点直接拒绝;第二道在 lesson_gen.generate_for_node,node 无效/无名称直接返回 error。
1 | # 修复:断根——无效节点不再兜底生成 |
2.5 代码卫生:23 个临时脚本
项目根目录堆积了 23 个一次性脚本:fix_01a.py、batch_review_all.py、cleanup_all_misplaced_objectives.py……这些是之前调试时写的,修完问题后没人清理。
它们不参与运行时(不会被 import 或调用),但会:
- 污染
ls输出,让新 contributor 困惑 - 掩盖真正的项目结构
- 可能包含硬编码路径或临时逻辑
修复:git mv 到 scripts/archive/——保留历史可回滚,但不再污染根目录。
2.6 注册:公开注册无防护
/register 端点完全公开,任何人可以注册账号。没有验证码、没有频率限制、没有邀请码机制。
修复:加环境变量开关 EDU_ALLOW_REGISTER,默认关闭。
1 | # 修复前:任何人都能注册 |
3. 修复节奏:同一天,7 个 commit
所有修复在 8 月 16 号一天内完成。commit 顺序经过设计——先修最严重的安全问题,再修业务逻辑,最后清理代码卫生:
| 时间 | Commit | 修复内容 | 优先级 |
|---|---|---|---|
| 12:35 | 23cb699 |
垃圾教案断根(n0/n1/n2 来源) | P1 |
| 14:56 | 7a68530 |
付费墙 API 层防绕过 | P0 |
| 14:56 | 2ae0ed4 |
RedeemStore 部分写入回滚 | P0 |
| 15:07 | ade8631 |
Embedding 密钥环境变量化 | P1 |
| 15:08 | 95c1efe |
归档 23 个临时脚本 | P2 |
| 15:09 | 3a46de4 |
公开注册加开关 | P1 |
6 个 commit,从发现到修复不到 3 小时。 审计报告本身就是最好的 bug tracker。
4. 踩坑记录:AI 产品审计的 3 个特殊陷阱
陷阱 1:前端 ≠ 安全边界
传统 Web 开发有个常识:后端校验才是真正的安全边界。但 AI 产品更容易犯”前端付费墙”的错误,因为 AI 产品的交互往往是对话式的——用户在聊天窗口里提问,系统返回答案。开发者很自然地在对话逻辑里加”如果没付费就不回答”,但忘了 API 端点本身是裸的。
规则:AI 产品的每一个内容生成端点,都必须有独立的权限校验。不能依赖前端 UI 或对话流程来”挡住”未授权访问。
陷阱 2:LLM 兜底 = 垃圾数据来源
a2a_bridge 的”空节点兜底”逻辑本意是好的——查不到节点就用空的 KnowledgeNode 继续生成,避免报错。但这导致了无效节点名也能生成教案,产生了 n0/n1/n2 这些幽灵数据。
AI 系统特别容易犯这个错误:为了让 LLM 流程”跑通”,加了大量 fallback 和兜底逻辑。但每一个兜底都可能产生垃圾数据,而垃圾数据会污染后续的训练/评测/统计。
规则:AI 系统的兜底逻辑必须有明确的”拒绝路径”。查不到就是查不到,不要用空数据假装查到了。
陷阱 3:部分写入 = 数据不一致地狱
RedeemStore.redeem() 的 bug 本质是一个经典的分布式系统问题:两个独立写入没有原子性保证。在传统 Web 开发中,你会用数据库事务;但在 AI 产品的轻量级架构中(JSON 文件 + 内存态),事务往往被忽略。
更隐蔽的是:这种 bug 不会报错——函数返回失败,但部分数据已经落盘。用户看到的是”兑换失败,请重试”,但实际上 codes 文件已经被修改了。
规则:任何涉及多个文件/状态的写入操作,都必须有回滚机制。要么用临时文件 + rename(原子替换),要么在 catch 块里恢复已修改的状态。
5. 总结:审计是最好的老师
这次审计教会我三件事:
AI 产品不是”功能跑通就安全”。付费墙、权限校验、数据一致性——这些传统 Web 开发的基本功,在 AI 产品中同样重要,甚至更重要(因为 AI 产品的 API 端点更多、数据流更复杂)。
审计报告本身就是最有价值的文档。它比设计文档更真实(因为是基于实际代码和数据写的),比 bug tracker 更全面(因为是系统性扫描而非逐个修复)。
修复的顺序比修复的速度更重要。先修 P0(付费墙绕过、数据不一致),再修 P1(密钥暴露、垃圾教案),最后修 P2(脚本清理、注册开关)。一天 7 个 commit,每个都有明确的优先级。
一句话 takeaway:给你的 AI 产品做一次审计吧——不是为了证明它安全,而是为了发现它哪里不安全。
相关资源
- 《我给 AI 当”教务主任”:手搓一个自动备课系统》 — 灵犀学堂的备课系统架构
- 《学生 8 天没练,掌握度从 83% 归零》 — 遗忘曲线引擎实录
- OWASP API Security Top 10 — API 安全的行业标准









