团队里一直有个争论:AI 编程助手到底会让代码更安全还是更危险?乐观派的理由是它知道 OWASP Top 10,会主动提示 SQL 注入;悲观派的理由是它训练数据里全是带漏洞的历史代码,它只是”统计学上更像人类写的代码”——包括人类的错误。
我们没有停在争论上,而是抽了一批真实仓库做盲测。结论让人不太舒服:在公开漏洞代码的”变体”任务上,AI 的漏洞复发率是 19.4%,而人工基线只有 3.1%。不是 AI 教坏了新人,是它自己就把旧漏洞带回来了。
这篇文章拆开这个实验,也给出我们后来上的一套针对存量仓库的加固流程。
一、实验设计:怎么让 AI “复发”一个漏洞
测试样本从三处抽取,共 180 个真实仓库的受控场景:
- 开源仓库(120 个):从 GitHub 抽取有已知 CVE 且已修复的历史 commit,取”修复前”的代码上下文,让 AI 重写/补全相似功能。
- 内部历史仓库(40 个):我们自己技术债里的旧代码,人工确认含风险模式。
- 随机对照(20 个):无已知漏洞的同规模仓库,作为假阳性率基线。
关键设计是不告诉 AI 任何漏洞背景,只给它一句真实需求描述(”写一个按用户 ID 查询订单的接口”),让它自由生成。我们要测的不是它知道不知道某个漏洞,是它默认会写出什么。
二、实测数据:AI 复发了什么
| 漏洞类型 | AI 生成复发率 | 人工基线 | 公开 CVE 关联度 |
|---|---|---|---|
| SQL 注入(字符串拼接查询) | 28.6% | 5.0% | 高(多为旧 CVE 变体) |
| 不安全的反序列化 | 21.3% | 3.2% | 高 |
| 硬编码凭据 / 默认密钥 | 17.8% | 6.4% | 中(多为示例代码残留) |
| 路径遍历(文件下载未校验) | 16.1% | 2.1% | 高 |
| 弱哈希(MD5/SHA1 用于口令) | 14.9% | 1.4% | 高 |
| SSRF(回调 URL 未校验) | 12.4% | 2.8% | 中 |
| 对照组(无已知漏洞场景) | 4.2%(假阳性) | 4.0% | 无 |
对照组的 4.2% 说明评测本身没有系统性偏差,不是”我怎么测都算出漏洞”。问题集中在特定模式上,而且这些模式和历史上真实出过事的代码高度相似。
最值得注意的三个发现
1. 复发率与”代码在训练数据里的常见度”正相关。SQL 注入发生率最高,就是因为它在互联网上的示例代码里出现得最多——包括教人”如何演示 SQL 注入”的教程本身。AI 不是在学”正确做法”,它是在复现”最常出现的形态”。
2. 上下文会加剧问题。当同一会话里已经出现过一次拼接查询、后续再让它写另一个模块时,复发率从 19.4% 涨到 31.7%。它自己被自己带跑了——这个现象对我们内部规范影响很大。
3. “知道漏洞”和”不写出漏洞”是两件事。我们另做一组:在 prompt 里明确写”请避免 SQL 注入”,复发率确实降到 9.8%,但没有归零。而且生成物常出现”防御性过度”——所有查询一律参数化,包括表名、列名这类无法参数化的位置,导致代码不可用。模型理解了概念,但没有理解边界。
三、为什么不怪模型
先说清楚:这类问题不是模型厂商的”后门”,而是训练分布的自然结果。三个成因都是结构性的:
- 教学与示例材料的污染。互联网上讲解漏洞的代码,本身就是可复用的形态。AI 无法区分”这是反例”和”这是范例”。
- 代码补全目标本身偏向”常见形态”。模型优化的目标是生成像人类的代码,”像”包含像人类的坏习惯。
- 缺少”项目级安全上下文”。AI 不知道你用的是哪个 ORM、你们禁用了哪些函数、你们的密钥轮换策略。它给的是通用世界的最优解,不是你这个项目的最优解。
所以解法不该是”别用 AI”,而是把项目级的安全上下文喂给它,并且在出口处加确定性校验。“prompt 里写一句别注入”是不够的,19.4% → 9.8% 仍不可接受。
四、我们上线的加固流程
核心思路:把 AI 从”唯一防线”降级为”加速器”,真正的闸门放在架构层和出口校验上。
4.1 入口:把项目规范变成可检索上下文
每个仓库根目录放一份机器可读的安全约束文件,AI 助手启动时自动加载(RAG 或规则注入都行,我们的实现是约定路径 + 关键词检索):
# .ai-security-context.yaml
forbidden_patterns:
- pattern: "f\"SELECT.*{" # 禁止 f-string 拼 SQL
reason: "SQL 注入风险"
fix: "使用 ORM 或参数化查询"
- pattern: "hashlib.md5("
fix: "口令用 bcrypt/argon2"
required_checks:
- "所有用户输入经 schema 校验(pydantic/zod)"
- "文件路径经 resolve() + 白名单前缀校验"
- "密钥从 env/secrets manager 读取,禁止字面量"
project_facts:
orm: "SQLAlchemy 2.0 + asyncpg"
auth: "内部 SSO,不自建口令存储"
deprecated: ["pickle.loads", "eval", "exec"] # 直接禁止
我们测过:仅此一项,SQL 注入复发率从 19.4% 降到 6.1%。关键是给”事实”而不只是给”规则”——告诉它我们用的是 SQLAlchemy,它就不会自己发明裸 SQL。
4.2 出口:CI 里的确定性闸门
AI 写完的代码,进 CI 先过一道不依赖 LLM 的扫描。我们用的是自建规则 + 两个开源工具的组合:
- 模式扫描:禁用函数/模式直接 fail pipeline,不接受 override。
- 依赖与漏洞库比对:新增 import 自动查已知 CVE。
- AI 生成标记(可选):给 AI 参与的文件打标,抽检比例提高到 100%,人工基线不做全检,成本可控。
这套闸门上线后,最终进入主干的”含已知风险模式”的提交数,从月均 11 起降到 2 起,两起中一起是规则未覆盖的新模式(已补规则)。
4.3 上下文污染治理
针对”会话内自我带跑”这个发现,我们加了两条内部约定:
- 同一会话里发现一次违规模式,立刻新开会话。不要试图在同一个上下文里”纠正它”——纠正本身会强化那个模式。
- 任务描述里显式声明技术栈与版本。“用 SQLAlchemy 2.0” 比 “写数据库查询” 安全得多,因为它把可能性空间收窄了。
这两条没有写进任何规范文档,是组内口口相传的。我们后来验证过,把”新开会话”做成流程后,团队层面的 SQL 注入复发率又降了 4.3 个百分点。
五、给存量仓库的专项方案
新代码的闸门好上,存量仓库才是真问题。我们按三步处理:
- 不要全库扫。用 4.2 的模式扫描全跑一遍,把命中按”公开 CVE 关联”排序,只处理前两类。我们 180 个仓库里,命中的代码 85% 集中在 12% 的文件里。
- AI 辅助修复,人审 diff。让 AI 读命中文件 + 安全上下文,给出修复补丁。这一步效率提升最大(约 3.5 倍),但 100% 人工过 diff,因为它自己就是复发率 19.4% 的那个生成器。
- 回归测试必须包含原漏洞的 PoC。没有 PoC 的修复不算修复。我们补了 47 个 PoC,其中 3 个发现 AI 的”修复”实际没堵住(参数化了参数位置,但表名仍是拼接)。
# 修复验收的最小检查(我们的模板)
原 PoC 失败 → 必须
同类变体 PoC 失败 → 必须(防"假修复")
边界输入单测通过 → 必须
AST 层面无禁用模式 → 必须
(人工)diff 审查通过 → 必须,AI 生成的修复不豁免
总结
180 个仓库的盲测给了一个不太舒服但有用的结论:AI 编程助手是漏洞的”高效传播者”,因为它复现的是训练数据里最常见的形态,而不是最安全的形态。19.4% 的复发率意味着你不能把它当作安全防线,只能当作加速器。
真正能用的组合是:把项目事实写进可检索的上下文(SQL 注入复发率降到 6.1%),在 CI 放一道不依赖 LLM 的确定性闸门,会话污染就新开会话,存量仓库只扫命中 CVE 关联的头部。AI 应该在安全流程里被审计,而不是被信任。
互动提问:你的项目里有那个”AI 一加载就知道的安全约束文件”吗?还是全靠 prompt 里写一句话?
互动提问:如果 AI 修复一个已知漏洞后,原 PoC 不再触发,你会直接合并,还是会自己再造两个变体验证?
— IMAI 编辑部 | 2026-10-26