摘要

1) 一句话总结

Kiro 漏洞修复工作流采用“基于属性的代码演进”方法,通过明确界定并自动化测试漏洞触发条件与保留边界,防止 AI 智能体在修复漏洞时过度修改或破坏正常运行的代码。

2) 核心要点

  • 核心痛点:AI 智能体缺乏对代码修改边界的隐式认知,常在修复漏洞时过度重构、添加冗余的防御性检查,甚至破坏正常代码。
  • 基于属性的代码演进:该方法论将输入空间明确划分为两部分:“满足漏洞触发条件(需修改)”和“不满足触发条件(需保持原样)”。
  • 关键定义(C 与 P)
    • 漏洞触发条件(C):明确界定漏洞何时显现。
    • 后置条件(P):定义当 C 成立时,代码应执行的正确操作。
  • 双重属性约束
    • 修复属性(C ⟹ P):当触发条件成立时,修补后的代码必须满足预期行为。
    • 保留属性(非 C ⟹ 保持不变):当触发条件不成立时,修补后的代码行为必须与原代码完全一致。
  • 前置审查机制(Checkpoint):在编写任何代码或测试前,Kiro 会先输出 C、P 以及根本原因假设,交由人类工程师审查和调整。
  • 测试驱动与差异测试结合:在修复前,先在未修复代码上运行测试。漏洞条件测试预期失败(红),保留测试预期通过(绿,作为行为基线);修复后两者均需通过。
  • 基于属性的测试(Property-based testing):使用 Hypothesis 库自动生成数百个随机输入(如随机树结构)来验证属性,覆盖手动单元测试难以触及的组合空间。
  • 规模化验证:该工作流适用于大型代码库,例如成功定位并修复了 Apache RocketMQ HeartbeatSyncer 中因键值不匹配导致的内存泄漏漏洞(仅需一行代码修复,但配备了严密的保留测试)。

3) 风险与局限性(基于原文)

  • 非功能性问题的适用性差:对于性能问题或竞争条件等非功能性漏洞,由于其具有非确定性且需要时间维度的推理,目前很难将其表达为可测试的属性声明。
  • 依赖清晰的漏洞报告:如果漏洞报告过于模糊,导致无法推导出明确的漏洞触发条件(C),智能体将无法构建边界并在编码前发出警告。
  • 假设与副作用风险:如果 Kiro 提出的根本原因假设错误,或者修复代码产生了副作用,将导致漏洞条件测试或保留测试失败,需要重新调查和缩小补丁范围。

正文

大多数研发团队都会遇到这样一种情况:当你要求 AI 智能体(Agent)修复一个漏洞时,它可能会重构三个辅助函数,添加防御性的空值检查,并为原本已经能通过的边缘情况编写几十个新测试。更糟的是,它甚至会修改应用程序中原本运行良好的部分。你本想要一把精准的“手术刀”,结果却得到了一把乱砸的“大锤”。

与人类相比,AI 智能体添加保护子句和防御性错误处理的概率几乎是人类的两倍。当我们人类工程师会问“为什么这里是空值?”时,智能体只会简单地加上 if (x == null) 然后继续。如果没有适当的约束,不断的迭代只会让情况变得更糟:你和智能体交流得越多,它就越偏离最初的意图。真正的修复方案(如果它碰巧找到了的话)往往被掩盖在大量不必要的代码更改之下。

这个问题的核心在于:你和智能体对于“哪些需要修复”与“哪些需要保持原样”的边界认知并不一致。为了让这个边界变得明确,我们构建了 Kiro 的漏洞修复工作流。该工作流基于一种我们称之为**“基于属性的代码演进”(Property-aware code evolution)**的方法。

基于属性的代码演进

每一次漏洞修复都包含双重意图:修复存在缺陷的行为,同时保留其他所有正常行为。这种意图将输入空间进行了划分,但这种划分通常是隐式的。我们可以将其变得明确且可测试。

漏洞触发条件(Bug condition)

漏洞触发条件(简称 C)定义了漏洞何时会被触发。它将输入空间一分为二:

  • 满足 C 的场景:漏洞显现的地方。这是你希望发生代码更改的区域。
  • 不满足 C 的场景:行为正确的地方。这是你希望保持原样的区域。

例如,在二叉搜索树(BST)中删除节点时,如果右子节点没有左子树会导致程序崩溃,那么条件 C 就是:节点有两个子节点 且 node.right.left 为 None。除此以外的任何删除场景都落在 C 之外,不应被触碰。

经验丰富的工程师在思考时通常会隐式地考虑 C。但如果没有将 C 作为一个明确的、共享的产物,就无法保证智能体理解的边界与你一致。当 C 保持隐式时,通常会发生三种错误:

  1. 智能体偏离边界:即使漏洞报告非常精确,智能体也没有关于边界的持久记录。在每一步操作中,它都会从头重新解释这个边界,经过多个步骤后,这些解释就会偏离最初的意图。
  2. 智能体凭空捏造边界:当漏洞报告模糊时,智能体会像任何工程师一样,用它最好的猜测来填补空白。区别在于,智能体不会明确展示这些猜测。当你能在代码审查中发现这种不匹配时,补丁往往已经基于错误的边界构建完成了。
  3. 智能体无法验证其是否遵守了边界:如果没有明确的 C,就没有系统的方法来检查其他功能是否仍然正常。智能体可以检查它的修复是否生效,但无法检查自己是否越界。

因此,C 划定了边界。但仅有 C 是不够的。C 告诉我们漏洞何时触发,但没有说明“修复”意味着什么。**后置条件(Postcondition,简称 P)**填补了这个空白:它定义了当 C 成立时(即针对触发漏洞的输入),代码应该执行什么操作。对于上述导致崩溃的 BST 删除操作,P 就是:删除操作不崩溃、成功移除节点,并保持 BST 的不变性。

如果没有 P,智能体可能会简单地用 try/except 压制错误并声称已修复。P 迫使智能体与真正的“正确”标准保持一致。

修复属性与保留属性

在“基于属性的代码演进”中,我们在编写代码之前先定义属性。属性是一个可测试的声明:对于满足特定条件的所有输入,某种保证必然成立。我们利用漏洞触发条件 C 和后置条件 P 来定义两个属性:

  • 修复属性(C ⟹ P):当 C 成立时,修补后的代码满足 P。
    • 示例:修复属性声明“在节点有两个子节点且 node.right.left 为 None 的树上,删除操作满足 P”。我们可以通过在这样的树上运行删除操作来检查。如果发生崩溃,则该属性验证失败。
  • 保留属性(非 C ⟹ 保持不变):当 C 不成立时,修补后的代码行为与原代码完全一致。
    • 示例:保留属性声明“在所有其他树上,删除操作的行为保持不变”。通过在修复前后对 C 之外的树运行删除操作来检查。如果行为发生改变,则该属性验证失败。

这两个属性共同覆盖了整个输入空间,并约束了智能体编写修复代码的方式。任何补丁都必须在不破坏保留属性的前提下通过修复属性。我们将这种方法论称为基于属性的代码演进

Kiro 的漏洞修复工作流在底层正是使用了这种方法论。Kiro 会提出漏洞触发条件、后置条件,以及修复和保留属性。你与它共同完善这些内容,随后 Kiro 生成的规范、测试和修复代码都将源自这些属性。

Kiro 漏洞修复工作流实践:二叉搜索树删除漏洞

假设你向 Kiro 提交了一个经典的关于数据结构漏洞的报告,并启用了漏洞修复工作流。Kiro 不会急于生成补丁。在编写任何代码之前,它会先划分出有漏洞和无漏洞的场景,提出根本原因假设,并对该假设进行测试。

1. 漏洞修复文档

Kiro 分析漏洞报告并生成一份包含三类需求的漏洞修复文档:当前缺陷行为、预期修复结果,以及必须保留的未更改行为。这反映了由漏洞触发条件 C 定义的边界划分。缺陷和修复需求针对的是触发漏洞的输入,而保留需求则明确了绝不能改变的特定行为。

2. 设计:触发条件与根本原因假设

漏洞修复文档用自然语言划分了场景。接下来,Kiro 会将其形式化,并调查漏洞存在的原因。

  • 形式化边界划分:Kiro 从缺陷和修复需求中提取出漏洞触发条件 C,并将“已修复”的含义形式化为后置条件 P。
  • 追踪根本原因:确立 C 和 P 后,Kiro 会阅读代码库以构建根本原因假设:为什么满足 C 的输入会崩溃而不是满足 P?它会追踪满足 C 的输入的执行流。
  • 提出假设:例如,假设 _find_min 接收到了 node.right.left 而不是 node.right。当 C 成立时,node.right.left 必然为 None,因此调用总是会崩溃。
  • 审查点(The Checkpoint):在编写任何代码或测试之前,Kiro 会将 C、P 和假设提交给你审查。此时尚未生成任何代码。如果 C 太窄、太宽或针对了错误的场景,你可以驳回并修改它。如果假设是错误的,下一阶段的测试会捕获它:针对未修复代码的修复属性测试应该因 AttributeError 而失败。如果它们因其他原因失败,或者根本没有失败,则说明假设被推翻,Kiro 会在编写修复代码前重新分析。

根本原因假设让设计阶段不仅仅停留在文档层面,它是一个可证伪的预测。随后的整个测试策略都是为了证实或证伪这个假设。

3. 任务计划:测试假设

Kiro 现在有了一个假设。在编写修复代码之前,任务计划会先对其进行测试。Kiro 会先在未修复的代码上运行所有测试,应用修复,然后重新测试。计划分为三个任务:

  • 任务 1:Kiro 为 C 范围内的输入编写漏洞条件测试,并编码预期行为(P)。在未修复的代码上运行这些测试,它们会失败。这证实了漏洞确实存在于 C 预测的地方。
  • 任务 2:Kiro 在 C 范围外的输入上运行未修复的代码,记录实际行为,并编写断言该行为的保留测试。这些测试在未修复的代码上应该全部通过。
  • 任务 3:Kiro 根据根本原因假设修补代码,并重新运行漏洞条件测试和保留测试。之前失败的漏洞条件测试现在通过了——说明修复有效。保留测试仍然通过,因为其他任何东西都不应该被改变。如果漏洞条件测试失败,说明假设错误,Kiro 会标记并重新调查;如果某个保留测试状态翻转,说明修复产生了副作用,Kiro 会缩小补丁的范围。无论哪种结果都具有指导意义。

这结合了测试驱动开发(TDD)的“红-绿循环”与差异测试(Differential testing)。漏洞条件测试在修复前是红色的,修复后是绿色的。保留测试记录未修复代码的行为,并断言修复后的代码在相同输入下行为一致;未修复的代码本身充当了规范。

4. 修复前的测试

这两个测试套件都通过 Hypothesis 库使用基于属性的测试(Property-based testing)。Kiro 声明修复和保留属性,并使用 Hypothesis 生成数百棵随机树来检查它们,而不是为特定的树编写测试。

为什么使用基于属性的测试?因为漏洞条件依赖于树的结构(特别是 node.right.left 是否存在),这种结构呈组合式变化。单元测试需要手动构建几十棵树来覆盖它,而基于属性的测试会自动探索该空间,生成数百棵树,覆盖手写测试套件难以触及的结构组合。

  • 漏洞条件测试(检查修复属性):编码了修复属性。在未修复的代码上,由于尝试访问 None.left,测试会因 AttributeError 失败。
  • 保留测试(检查保留属性):编码了保留属性(针对叶子节点删除、单子节点删除等 C 不成立的情况)。在未修复的代码上,测试通过,这确立了基线。修复后,该测试必须依然通过。

5. 执行修复

在漏洞条件测试证实了假设,且保留测试捕获了基线之后,Kiro 会编写一行修复代码。重新运行两个测试套件后:漏洞条件测试通过(验证修复有效),保留测试依然通过(验证没有改变其他内容)。

规模化应用:RocketMQ 内存泄漏案例

Kiro 同样适用于大型代码库。以 Apache RocketMQ 的 HeartbeatSyncer 内存泄漏为例:该组件在一个并发映射(Map)中跟踪连接的消费者。条目在注册时添加,在注销时移除。但移除操作从未成功过,导致映射无限制增长。

为了识别和修复这个漏洞,Kiro 遵循相同的工作流。根本原因假设是一个键值不匹配问题:插入键在前面加上了消费者组,但移除键没有。它们永远无法匹配,导致每次注销都是无效操作。漏洞条件 C 是任何包含非空 ClientChannelInfo 参数的有效 CLIENT_UNREGISTER 事件。

修复代码依然只有一行,但验证保留属性要困难得多。同一个监听器有五条不同的代码路径(空参数、非 ClientChannelInfo 参数、多组注册等)。插入逻辑和其他事件类型也必须保持原样。C 之外的每个场景都会获得自己的保留测试,并在 Kiro 应用修复之前编写并确保通过。

结论

借助基于属性的代码演进,你和 Kiro 遵循同一份“契约”工作。Kiro 起草边界和假设,你可以提出异议、重新划定边界、缩小范围或要求采用不同的方法。在编写代码之前,你们已经就“什么需要改变”和“什么保持不变”达成了一致。

当这份契约出现问题时,工作流会让问题变得显而易见。如果漏洞报告太模糊无法推导出 C,Kiro 会在写代码前发出警告。如果根本原因假设错误,漏洞条件测试会捕获它。如果修复有副作用,保留测试会捕获它。每一次失败都会告诉你下一步该做什么。

基于属性的代码演进最适合那些预期行为可以表达为功能性、可测试属性的变更:逻辑错误、边缘情况、运行时异常、数据处理漏洞。对于性能或竞争条件等非功能性问题,表达属性较为困难,因为它们通常具有非确定性,需要时间维度的推理。如何将它们最好地表达为可测试的声明,仍是一个悬而未决的问题。

漏洞修复只是基于属性的代码演进的一个应用场景。功能添加和重构同样具有双重意图:在边界内改变行为,保留其他一切。这个边界可以通过可测试的属性来强制执行。将该方法论应用到漏洞修复之外的领域,是我们目前活跃的研究方向。

下次提交漏洞时请记住:你正在划定一条边界。与 Kiro 一起划定它,让属性确保你的修复始终站在正确的一边。

延伸阅读

  • State of AI vs. Human Code Generation Report
  • Drift No More? Context Equilibria in Multi-Turn LLM Interactions
  • Does your code match your spec?
  • The Hypothesis Property-Based Testing Library
  • QuickCheck
  • SWE-PolyBench
  • Differential testing

致谢

感谢 Aaron Eline、Anjali Joshi 和 Margarida Ferreira 在基于属性的代码演进方法论以及 Kiro 漏洞修复工作流上付出的努力。

新增规范类型:修复漏洞并在现有应用程序之上进行构建

相关文档

关联主题