生成的代码漏洞不是随机的,按CWE分类高度集中;根本原因是公开代码库中不安全模式更常见,且模型无法自行推断缺失的安全检查。
生成代码的不安全性并非随机产生。漏洞会聚集成几个有限的类别,每个类别都可以从代码生成方式的某些特定因素中预测出来。
三个结构性原因
语料库中包含漏洞模式。公开代码涵盖了过去十年的教程、Stack Overflow 回答和练手项目。对于若干 CWE 类别,不安全版本在该语料库中确实是更常见的版本,因此概率最高的续写就是有漏洞的那个。这不是模型的 bug;模型运行正常。
安全主要关乎缺失的部分。授权检查、边界限制、超时设置。模型补全的是存在的内容;提示词中没有任何内容描述某个功能时暗示应该存在某个检查。缺失没有概率质量。
提示词几乎从不说明威胁模型。"写一个返回发票的端点"不包含对手。加上"id 来自不可信客户端且用户只能看到自己组织的发票",输出就会发生实质性改变——这是性价比最高的缓解手段,同时也证明模型具备相关知识,只是没有被要求。
按 CWE 索引,可以映射到你已运行的任何扫描器。本页代码是作为每个类别的说明示例而编写的,不是从任何模型的输出中收集的。
最重要的那一类
注入和弱加密可以被扫描器捕获。缺失授权则不能,因为没有可匹配的模式——正确代码是项目特定的,而错误代码则是完全正常的查询。
# 可信、地道、通过 review,却是一个 IDOR。
@app.get("/invoices/<invoice_id>")
@login_required
def get_invoice(invoice_id):
inv = Invoice.query.get_or_404(invoice_id)
return jsonify(inv.to_dict())
# 检查从未出现在 diff 中,所以没人注意到它缺失。
@app.get("/invoices/<invoice_id>")
@login_required
def get_invoice(invoice_id):
inv = Invoice.query.filter_by(
id=invoice_id, org_id=current_user.org_id # <- 作用域,而非过滤
).first_or_404()
return jsonify(inv.to_dict())
注意是什么让第一个版本显得可信:它做了认证。有一个装饰器,拼写正确,reviewer 检查"是否有保护"时得到肯定答案。认证存在,授权缺失,两者足够邻近,在粗略审阅时能够互相替代。
结构性修复不是 review,而是让无作用域的查询无法写出——一个要求 tenant 的 repository 层、数据库中的行级安全策略、一个已经携带作用域的基础查询对象。这样模型的常规补全就是安全的,因为模型复制的是你代码库中的常规补全。
已发表研究发现的内容
三篇论文值得了解,且它们结论不一——这本身就是有价值的发现。
Pearce、Ahmad、Tan、Dolan-Gavitt 和 Karri,"Asleep at the Keyboard"(arXiv 2021,IEEE S&P 2022)。围绕 MITRE 顶级 CWE 构建了 89 个场景,用 GitHub Copilot 生成了 1689 个程序。大约 40% 被评估为存在漏洞。重要的设计注意事项:场景是刻意设计来诱导漏洞的,评估是按程序而非按开发者进行的,且模型来自 2021 年。它确立了这些类别真实存在且可被触发,但不是你应该在代码库中预期的比率。
Perry、Srivastava、Kumar 和 Boneh,"Do Users Write More Insecure Code with AI Assistants?"(ACM CCS 2023)。一项包含 47 名参与者的用户研究,涵盖多个安全相关任务。有助手辅助的参与者编写了安全性较低的解决方案,并且——值得记住的部分——对自己的代码安全性更有信心。信心差距是这些 bug 通过 review 的机制。
Sandoval 等人,"Lost at C"(USENIX Security 2023)。一项关于低级 C 的用户研究,发现有辅助参与的参与者中严重安全 bug 没有显著增加。不同的语言、不同的任务、不同的人群。每当有人声称问题已经定论时,就引用它。
综合来看:这些类别是真实存在且可触发的,对在职开发者的影响很大程度上取决于任务和语言,没有人有一个能迁移到你代码库的数字。
经得起检验的控制措施
CI 中的静态分析,而非 review 中的。Semgrep 或 CodeQL 以确定性方式在每次提交时免费捕获可模式匹配的类别。任何扫描器能判断的东西都不应该消耗人类注意力或模型调用。
提示词中的威胁模型。一句话说明不可信输入和必须成立的条件。这是这个领域中高性价比的缓解措施。
代码库指令文件中的安全规则,陈述为禁止条款并附带本地替代方案:永远不要通过插值构建 SQL,使用 db.scoped(org_id);永远不要用 Math.random() 处理用户可能猜到的东西,使用 crypto.randomUUID()。这个文件应该包含什么本身值得写一整页。
让不安全的调用不可用。Lint 规则禁止逃生舱、作为唯一导出 HTTP 客户端的包装器、raw execute() 的 linter 规则。从代码库中移除漏洞模式,也就从模型的本地先验中移除了它。
将 agent 工具访问作为独立问题对待。模型编写不安全代码和模型执行攻击者提供的指令是不同的威胁;OWASP LLM 应用安全列表涵盖的是第二种。
Reviewing AI-Written Code: A Checklist
AI in CI: Automated Review That People Don't Mute
AI for SQL and Data Work