引言
在过去几年里,软件开发领域发生了巨大的变化。GitHub Copilot、StarCoder 和 GPT-4 等大型语言模型 (LMs) 已成为开发者不可或缺的助手。它们能够自动补全函数、编写文档,甚至搭建整个应用程序的框架。然而,这种便利伴随着一个隐性成本: 安全性 。
最近的研究显示了一个令人担忧的趋势。由于这些模型是在海量开源代码库 (如 GitHub) 上训练的,它们在学习优秀代码的同时,也学到了其中的坏习惯。调查发现, 流行的编码助手生成的代码中,约有 40% 包含安全漏洞 。
想象一下,一位开发者要求 AI 编写一个处理数据库查询的函数。如果 AI 是在不安全的代码上训练的,它可能会生成一个容易受到 SQL 注入攻击的函数。这为在银行或医疗保健等安全敏感领域部署 AI 造成了巨大的瓶颈。

如上图 1 所示,我们的目标是接受一个标准的提示 (例如“从数组中获取值”) ,并确保模型生成“安全代码” (检查数组边界) ,而不是“不安全代码” (可能导致缓冲区溢出) 。
在这篇文章中,我们将深入探讨一篇名为 “SecCoder: Towards Generalizable and Robust Secure Code Generation” 的研究论文。该论文提出了一种名为 SecCoder 的新方法,该方法无需昂贵的重新训练,即可引导模型编写安全代码。它利用了一种称为 上下文学习 (In-Context Learning, ICL) 的技术,结合 稠密检索器 (Dense Retriever) , 实时向模型展示“安全演示”。
核心问题: 泛化能力与鲁棒性
在了解解决方案之前,我们必须理解为什么之前的尝试都未能解决问题。
现有的方法通常依赖于 微调 (fine-tuning) 。 这意味着采用一个预训练模型,并在安全代码数据集上对其进行进一步训练。虽然这对训练集中包含的特定漏洞有效,但它存在两个主要局限性:
1. 缺乏泛化能力 (Generalizability)
软件漏洞有数百种类型 (由通用缺陷枚举 CWE 分类) 。一个经过微调以修复 SQL 注入的模型,在处理缓冲区溢出时可能仍然表现糟糕。如果模型遇到一个与微调期间看到的完全不同的“测试用例” (编码场景) ,它通常无法应用安全规则。这被称为 错配泛化 (mismatched generalization) 。
2. 缺乏鲁棒性 (Robustness)
第二个问题更为严重。模型可能会受到“攻击”或“投毒”。如果攻击者在训练阶段引入恶意数据,模型可能会被教导故意编写易受攻击的代码。现有的防御措施通常很脆弱;如果一个模型已经被有效地投毒,简单的提示工程很少能阻止它输出糟糕的代码。
解决方案: SecCoder
研究人员提出了 SecCoder , 这是一个旨在兼具泛化能力 (适用于新的、未见过的场景) 和鲁棒性 (即使底层模型有缺陷也能工作) 的框架。
SecCoder 的核心理念是: 不要只告诉模型做什么,要演示给它看怎么做。
SecCoder 不是改变模型的内部权重 (微调) ,而是利用 上下文学习 (ICL) 。 ICL 是大型语言模型仅通过观察提示中提供的示例来学习任务的能力。SecCoder 检索一段相关的安全代码片段,并在用户请求之前将其粘贴到提示中。这种“演示”引导模型遵循安全模式。
框架
SecCoder 框架在一个四步流程中运行。让我们分解一下这个架构。

如图 2 所示,该过程从扩展知识库开始,到选择正确的知识,进行集成,最后生成代码。
步骤 1: 扩展 (知识库)
首先,我们需要一个真理来源。系统维护一个 安全代码库 (\(S\)) 。 这是一个已知安全的代码片段集合。当发现并修复新的漏洞时,这些修复会被添加到数据库中。这使得系统能够即时更新其“安全知识”,而无需重新训练 AI 模型。
步骤 2: 演示选择 (检索器)
这是最关键的一步。我们不能简单地将 任何 安全代码喂给模型;它必须与用户当前的请求相关。如果用户想要编写一个文件处理函数,向他们展示一个安全的网络套接字函数是没有帮助的。
为了解决这个问题,SecCoder 使用了一个 稠密检索器 (Dense Retriever) 。 与简单的关键词搜索 (寻找匹配的单词) 不同,稠密检索器使用预训练的嵌入模型 (本论文中特指 “INSTRUCTOR”) 来理解代码的 语义含义。
以下是选择最佳演示的数学过程:
首先,系统将用户的输入提示 (\(x\)) 转换为向量嵌入 (\(x_{emb}\)) :

然后,它遍历安全代码库 (\(S\)) 。它将每个安全片段 (\(s\)) 转换为嵌入 (\(s_{emb}\)) ,并计算用户提示与安全片段之间的 余弦相似度 (Cosine Similarity) 。

目标是找到具有最高相似度得分 (\(max_{sim}\)) 的片段 (\(s_j\)) ,这意味着它在语义上最接近用户想要构建的内容。

通过使用余弦相似度,系统测量向量的“方向”,这有效地捕捉了两段代码之间的语义关系,而不仅仅是它们的长度或关键词重叠。
步骤 3: 集成
一旦确定了最有用的安全演示 (\(s_j\)) ,就需要将其与用户的原始请求 (\(x\)) 结合起来。
SecCoder 将它们拼接在一起,形成一个新的、增强的提示 (\(\hat{x}\)) 。集成通常遵循特定的模板 (例如将安全代码放在注释块中) ,以确保模型理解这是一个参考,而不是最终输出。

这一步利用了 上下文学习 能力。通过在被要求生成代码之前立即看到安全示例 \(s_j\),LLM 推断出它应该模仿演示的编码风格和安全模式。
步骤 4: 安全代码生成
最后,增强后的提示被输入到代码 LM 中。模型根据这个新上下文预测最可能的标记序列 (\(y_k\)) ,从而生成最终的代码输出 (\(y\)) 。

因为上下文现在包含了一个安全示例,输出的概率分布会向安全模式偏移。
实验设置与结果
研究人员对 SecCoder 进行了广泛测试,以验证它是否真的比现有方法更好。
- 测试模型: CodeGen (不同尺寸) 、SantaCoder 和 InCoder。
- 基线 (Baselines) :
- “None”: 没有任何安全干预的原始模型。
- SVEN: 一种使用连续提示调整 (微调) 来引导安全性的最先进方法。
- 指标: 通过安全检查的代码百分比 (使用 CodeQL) 。
结果 1: 卓越的泛化能力
首要问题是: SecCoder 能否针对它未明确训练过的漏洞来保护代码?
为了测试这一点,研究人员在“未见过的测试用例”上评估了模型——这些场景不存在于基线方法的训练数据中。

图 3 展示了 CodeGen 模型的结果。绿色柱状图 (“None”) 代表基础安全率,徘徊在 60-70% 左右。
- SVEN (橙色) : 显示出一些改进,但很有限,因为它难以泛化到这些未见过的案例。
- SecCoder-xl (蓝色) : 显示出安全性能的大幅提升,显著优于 SVEN。
- 组合 (粉色) : 有趣的是,当 SecCoder 被添加 在 SVEN 之上 时,性能甚至更高。这证明了 SecCoder 与其他防御方法是兼容的。
数据显示,在未见过的案例中,SecCoder 比原始模型平均提高了 12.07% 的安全性,比 SVEN 提高了 7.20% 。 这证实了提供实时演示比试图将安全规则烘焙到模型权重中更能有效地实现泛化。
结果 2: 抵御攻击的鲁棒性
下一个问题是: 如果模型被“投毒”了,SecCoder 还能工作吗?
研究人员使用了一个名为 SVEN_vul 的模型版本。该模型被故意攻击/训练以生成易受攻击的代码 (例如,制造内存泄漏或不当的输入验证) 。

如上表所示,受攻击的模型 (SVEN_vul) 的安全率极低 (降至 30-40% 范围) 。然而,当 SecCoder-xl 应用于这个受损模型时,安全率显著回升 (提高了大约 7-9%) 。
这是一个至关重要的发现。它表明 上下文学习 效应足够强大,可以覆盖被投毒模型学到的恶意倾向。显式的“安全演示”充当了护栏,强制模型回到安全路径上。
结果 3: 保持功能性
安全加固的最大风险之一是破坏代码的实用性。一段什么都不做的代码是绝对安全的,但也是无用的。我们需要知道 SecCoder 是否在让模型变安全的同时让它变“笨”了。
为了衡量这一点,研究人员使用了 HumanEval 基准测试,该测试评估代码的功能正确性 (Pass@k 指标) 。

图 4 比较了原始 CodeGen 模型 (“None”) 与增强版 SecCoder。
- 结果: 线条几乎重合。
- 含义: SecCoder 在提高安全性的同时 没有 降低代码的功能正确性。
这与像 SVEN 这样的方法形成对比,后者通常需要在实用性和安全性之间仔细权衡。SecCoder 自然地实现了这一点,因为检索到的演示是有效的、可工作的代码——所以模型同时学习了正确性和安全性。
为什么有效?检索的力量
你可能会问: 复杂的“稠密检索器”真的有必要吗?我们能不能只随机选一个安全示例?
研究人员进行了一项消融实验,比较了不同的检索策略:
- Random (随机) : 随机选取一个安全片段。
- BM25: 使用经典的基于关键词的搜索 (稀疏检索器) 。
- SecCoder (Dense): 使用向量嵌入 (语义搜索) 。

图 6(a) 显示了 检索准确率——具体来说,就是检索器找到与用户问题的 确切漏洞类型 (CWE) 相匹配的演示的频率。
- SecCoder-xl (橙色) 保持了比 BM25 (紫色) 高得多的准确率。
- 因为它使用语义嵌入,SecCoder 能够理解,当用户要求“数据库查询”时,需要一个关于“SQL 注入”的演示,即使确切的关键词不匹配。
图 6(b) 显示了找到相关匹配所需的 平均最小数量 。 SecCoder 比 BM25 更快找到正确的示例 (尝试次数更少) 。这一点至关重要,因为 LLM 的上下文窗口有限——我们不能把 50 个例子塞进提示里;我们需要 那一个 最好的例子。
真实世界案例
为了直观地展示这在实践中是什么样子的,让我们看一个具体的漏洞: CWE-022 (路径遍历) 。 当应用程序允许用户访问预期目录之外的文件 (例如 ../../etc/passwd) 时,就会出现此漏洞。

在上图 11 中:
- 左侧 (提示) : 用户想要编写一个
read()函数,该函数接收文件名并返回文件。一个简单的模型可能会直接打开文件,允许黑客请求../../secret_file。 - 右侧 (检索到的演示) : SecCoder 检索到了一个使用
flask.safe_join的片段。这是一个防止路径遍历的安全函数。
通过看到右侧的演示,LLM 理解了在生成的代码中应该使用 safe_join 或类似的逻辑,从而有效地消除了漏洞。
结论
大型语言模型在编码中的广泛采用是不可避免的,但盲目依赖它们是危险的。正如我们所见,在“野生”互联网上训练的模型吸收了数据中的安全缺陷。
SecCoder 指出了一条引人注目的前进道路。通过摆脱僵化的微调,拥抱 检索增强生成 (Retrieval-Augmented Generation) 和 上下文学习 (In-Context Learning) 的灵活性,我们可以创建出具备以下特征的编码助手:
- 泛化能力: 能够处理新的、未见过的漏洞。
- 鲁棒性: 即使底层模型存在潜在的安全缺陷或受到攻击,也能保持弹性。
- 实用性: 能够编写出既能工作又安全的代码。
也许 SecCoder 最有前途的方面是它的简单性。它不需要每次发现新漏洞时都消耗大量计算资源来重新训练模型。相反,我们只需将修复程序添加到数据库中,稠密检索器就会确保 AI 立即从中学习。随着 AI 在软件开发中的融合日益加深,像 SecCoder 这样的框架很可能会成为生成代码与生产系统之间的标准防火墙。
](https://deep-paper.org/en/paper/2410.01488/images/cover.png)