从“补丁”到“修复闭环”的范式转变

在 Shopify,一个好的代码补丁并不等同于漏洞已被彻底修复。虽然系统能自动生成依赖升级和初版修复方案,但无法独立驱动补救措施直至闭环。仓库状态在 PR 审核期间可能发生变化,开发者添加的上下文也可能改变修复逻辑,且 CI 通过并不能证明漏洞已从仓库头部移除。为此,Shopify 开发了名为 River 的 AI Agent,它驻留在公司 Slack 中,基于 World(Shopify 的单仓库)根目录运行。River 复用开发者相同的可重现开发环境、书面技能和工程惯例,这种共享基础使其从一开始就具备 Shopify 特有的上下文。我们无需重新教导 Agent 如何理解代码库,而是直接注入补救逻辑。工程师不再需要在代码、CI、Slack 和追踪器之间切换状态,可以专注于产品功能和需要人类判断的风险决策。River 负责核对原始发现、安全更新补丁、引入具备上下文的工程师,并在合并后检查仓库头部和依赖图以确认工作完成。

显著的效率提升与防御不对称性

自上线以来,River 在依赖和应用漏洞的补救管道中取得了显著成果。在前11天运行依赖工作流期间,开放问题的积压量下降了约 70%。其中约有三分之二为直接合并,其余部分被 River 确认为过时或已在别处修复。此外,通过新鲜度门控合并队列的安全合并比例从约 10% 提升至 80%。这一成果揭示了防御不对称性在代码差异层面的具体体现。攻击者可以容忍重复失败,而防守方尤其是组织必须在每次修复中保持生产行为的一致性。这种不对称性促使我们分享此工作,因为攻击者跨目标复用相同技术,而展示我们的工作有助于提高构建 Agentic 安全工作流的基准线。通过分享细节,我们希望帮助其他团队避免以艰难的方式学习。

工作流启动与状态验证机制

依赖修复与应用漏洞补救的工作流虽输入不同,但遵循相同的补救循环。依赖工作通常始于旨在升级脆弱依赖的 PR,而应用漏洞则始于 Agent 发现的安全问题及推荐修复。River 为每个应用漏洞创建独立的 Slack 线程,并将影响代码库同一区域的多个相关依赖升级 PR 分组到共享线程中。例如,在一次运行中,四个线程在分钟内相继建立,覆盖35个发现并独立处理。River 读取工作账本后,将每个条目视为需要验证的主张而非既定事实。它会对比活仓库、PR 和漏洞追踪器状态,移除看似开放但实际已合并或因系统关闭的 PR。对于依赖项,River 检查受影响代码库是否仍解析到脆弱版本;对于应用漏洞,它确认仓库头部代码行为是否与发现一致。如果另一个更改已经修复了问题,River 会记录证据并停止,因为基于过时前提构建的技术正确补丁不仅浪费工程时间,还可能无谓地改变生产行为。

修复迭代与 CI 结果处理

River 仅在确认漏洞仍然活跃时才接触代码。对于过时的依赖升级,它在仓库头部重放升级、使用内部工具重新生成锁文件,并修复由升级引发的机械故障。在一次包含8个依赖 PR 的批次中,River 重基了7个分支,对每个新头部重新触发 CI,并修复了一个因升级而触发的发布版本检查。值得注意的是,仓库头部的锁文件在 PR 审核期间已发生变化,因此 River 将每个升级嫁接至当前锁文件中,而非重放各分支副本,确保三个共享该锁文件的 PR 合并时未回退相邻更改。此外,每次重基都会创建使上一次结果无效的提交。River 丢弃了紧随强制推送后到达的 CI 通过通知(这些属于推之前的头部),并等待真正的裁决结果。这种对状态转换的精细处理,确保了修复的安全性和准确性。

智能重基线的判断逻辑:何时干预,何时放手

River 在处理 Pull Request (PR) 时并非盲目执行重基线(rebase)操作,而是基于对代码状态和人类协作流程的深度理解进行判断。在一个具体案例中,第八个 PR 并未被重基线,因为已有工程师接管了该分支并提交了新代码,且存在另一位正在进行的审查。若强行重基线将覆盖他人工作并预决设计争议,因此 River 选择保持原状不动。这种克制体现了系统对“人为介入”边界的尊重。在其余七个被 River 成功重基线的 PR 中,六个顺利通过 CI 并进入维护团队审批环节,仅需人工确认即可合并;而第七个 PR 因测试失败保持红色状态。这一结果差异表明,自动化流程的价值在于处理确定性任务,而非强行解决所有冲突。当系统识别出需要人类判断的复杂情境时,暂停比错误推进更为重要。

移交决策而非调查:上下文丰富的交接机制

当 River 遇到无法自主裁决的情况时,其移交(handoff)过程携带了丰富的上下文信息:当前代码头、尝试过的操作、证据显示的结果以及所有者需回答的最小化问题。这种机制确保了人类维护者无需在 Git、CI、Slack 和追踪器之间穿梭状态,而是直接基于证据做出产品层面的风险接受与合并决定。例如,某个保持红色的 PR 需要两个关键决策:是否部署新版本以及类型变更在运行时的行为。River 虽能收集证据,但无权裁定结果。“当你无法定义想要的结果或证明其正确的证据时,另一次编辑就是一场赌注而非修复。”

在另一个典型案例中,River 根据发现建议加固一个长期存在的特性。它确认了行为、准备了变更并更新了测试,但随后开发人员介入询问该特性的原始设计需求。River 回溯发现,该行为是多年前为解决特定问题而有意引入的,且当时已通过安全审查。随着平台级保护措施的完善,残余风险已降低,River 原先建议的修复反而会破坏原有流程。最终,River 推荐撤销自己的补丁。这一结果并非失败,而是证据变化导致建议更新。“有用的停顿将证据留在共享位置,明确边界,并路由一个可以恢复工作的决策。” 这种基于证据的闭环比未经解释的推送更能建立开发者信任。

验证修复闭合:从代码合并到记录一致性的全链路检查

安全问题的关闭不仅仅意味着 PR 的合并,更要求漏洞记录与账本(ledger)反映修复事实。River 在 PR 合并后持续监控依赖项,确保仓库头部不再使用脆弱版本。文中指出一个关键区分:“‘完成’意味着源系统记录结果,默认分支反映该结果,且追踪器和账本达成一致。” 在实际运行中,两个绿色的依赖 PR 均通过 CI:一个合并后移除了脆弱版本并关闭了待办项;另一个仍等待审查,导致脆弱版本保留在仓库头部且待办项保持开放。“两个修复都‘有效’,但只有第一个移除了漏洞。” 这强调了衡量标准应是实际的安全状态改变(如默认分支的纯净度),而非单纯的 PR 数量或绿色分支计数。若存在替代性修复或被拒绝的发现,同样需要提供同等水平的证据和归因。

工作流提示与代码控制的边界:从声明到保证的演进

当前的工作流由 River 的提示(prompts)和技能驱动,明确指定了完全枚举、仅草稿行为和人工合并权限。然而,“提示是表达判断的正确场所,而非表达保证的场所。” 在一次运行中,River 仅重新检查了最旧的进行中的声明并在报告中说明,这证明了“提示说如此”并非不变量。随着工作流成熟,分页、去重、当前头身份和会计等应成为代码中的确定性逻辑。提示则保留用于判断性调用,如确认代码是否仍表达报告的行为以及为何需要停止。“在安全领域,这比以往任何时候都更重要。任何决定漏洞是否关闭的规则必须在代码中强制执行。” 这种区分确保了系统的可靠性建立在可验证的代码逻辑之上,而非易变的自然语言指令。

核心实践与未来展望:建立可信任的代理修复协议

即使没有 Shopify 的内部工具,团队也可借鉴三个核心思想:编辑前重新验证(比较仓库头部、PR 状态和追踪器)、确保证据跟随代码(绑定测试到当前 SHA,合并后检查默认分支并协调追踪器)、以及将移交作为工作流的有意部分。River 的目标是构建一个完全自主的修复管道,连接漏洞发现与修复循环。“这种持久优势不仅在于产生更多补丁,更在于为管道中的每个过渡定义基于证据的协议。” 该协议明确了预期输入、持续条件、代理可执行动作及明确的停止点。通过这种方式,“我们的代理补丁工作转变为可扩展的可信任修复。” 衡量标准应从代理创建的补丁数量,转向每个发现是否达到了有证据支持的结果(修复、拒绝或升级)。

结论

River Agent通过复用开发者环境,将安全修复从被动补丁升级为闭环流程。其核心优势在于显著的效率提升:运行11天使积压下降70%,安全合并比例从10%跃升至80%。系统通过“编辑前重验证”和证据驱动的移交机制,有效解决了状态不一致问题,并在面对人类介入或历史设计时展现出自适应与克制。然而,其可靠性仍依赖提示词规范,且需区分PR合并与实际漏洞消除的差异。

该方案证明了Agentic工作流在防御不对称性中的价值,但尚未完全实现全自主。当前阶段需将判断逻辑从提示词迁移至代码以确保证据链的确定性。最终衡量标准应从补丁数量转向基于证据的结果闭环,即修复、拒绝或升级的明确归因。这为构建可扩展且可信任的安全代理协议提供了实证基础,强调了人机协作边界与状态一致性的重要性。