阅读需 8 分钟

AI 代理如何在安全控制下创建拉取请求

通过 API 创建拉取请求的 AI 代理需要严格的仓库范围、受保护分支、评审者路由、测试证据,以及针对每项变更的审计记录。

AI 代理如何在安全控制下创建拉取请求

通过 API 创建拉取请求的 AI 代理确实能节省工程时间,但前提是仓库把每个生成的变更都视为不受信任、且作者可追溯的贡献。边界其实很简单:代理可以准备提议中的变更,是否应该进入代码库,则由人工和仓库控制来决定。

我见过一些团队把注意力都放在模型的代码质量上,却忽略了代理周围的权限,结果让系统变得不安全。真正糟糕的失败通常都很普通。面向测试服务的任务落进了生产仓库。一个范围过大的令牌悄悄获得了所有项目的写权限。代理在超时后连续创建了十个几乎相同的拉取请求。有人因为标题听起来合理就合并了其中一个。

安全的设置不依赖代理是否足够谨慎。它会限制代理可以在哪里操作,要求变更走正常的评审流程,并留下足够详细的记录,以便日后调查。

仓库范围必须明确,并由机器强制执行

代理应获得一组明确命名的仓库权限,而不是先授予通配符组织范围,打算以后再收窄。仓库范围要回答一个具体问题:这个进程可以读取哪些代码库、在哪些代码库中写入分支,以及针对哪些代码库创建拉取请求?

不要把允许列表放在代理的指令文本中。提示词可以引导行为,却不能执行授权。持有仓库凭据或发起 API 请求的组件,必须拒绝不在允许列表中的仓库。

对每个允许的仓库,定义可用的基础分支和允许写入的命名空间。一个实用的记录如下:

repositories:
  - name: acme/payments-api
    base_branches: ["main", "release/2025.1"]
    write_branch_prefix: "agent/"
    pull_request_drafts: true
  - name: acme/docs
    base_branches: ["main"]
    write_branch_prefix: "agent/"
    pull_request_drafts: false

这个片段可以防止一种常见故障:代理收到“修复结账页面文案”的请求后,在它能够搜索的仓库中找到相似文件,因为凭据允许它写入,就把文件写到了那里。允许列表会把这种情况变成一次被拒绝的请求,而不是交给别人处理的清理任务。

范围也包括仓库操作。大多数代理只需要读取文件、创建分支、推送提交、读取检查结果,以及创建或更新拉取请求。它们很少需要修改仓库设置、注册 Webhook、修改受保护分支规则、添加部署密钥、管理成员或合并变更。不要因为一个方便的宽权限令牌把这些权限打包提供,就全部授予代理。

使用独立的机器人身份,不要使用开发者的个人访问令牌。个人令牌会让归属变得模糊,工作变化后也很难妥善处理,而且往往带有没人记得授予的权限。机器人身份能让你在行为出错时明确暂停一个操作者。

读取权限也需要像写入权限一样认真讨论。能够查看每个私有仓库的代理,可能会通过自身的日志、任务上下文和响应泄露源代码或配置。即使代理从未拿到直接写入凭据,也应给予它完成任务所需的最小仓库集合。

分支前缀是执行边界,不是命名偏好

代理只能在专用前缀下创建分支,例如 agent/,而且仓库服务器应强制执行这一限制。写在提示词中的约定迟早会被破坏,原因可能是格式错误的工具调用、重试漏洞,也可能是代理试图完成范围过大的任务。

保护 main、发布分支、环境分支,以及任何会自动部署的分支。代理不能向这些分支推送,也不能强制推送,更不能修改保护它们的规则。

分支名称要足够确定,便于调查,同时也要足够独特,避免冲突。可以加入任务引用,再加上短随机后缀或运行标识:

agent/OPS-1842-retry-payment-7f3a

不要让代理直接使用任务标题作为分支名称。标题可能包含密钥、客户名称、不安全字符或误导性措辞。应由控制器生成分支名称,再将其作为不可变值传给代理。

基础提交也需要明确规则。控制器开始运行时,应将批准的基础分支解析为提交 SHA 并记录下来。代理应从这个 SHA 创建分支,而不是从一次漫长的代码生成过程结束后 main 所指向的位置创建分支。这样不能消除偏移,但能让偏移可见且可复现。

一个分支只能包含与分配任务相关的提交。这意味着不能顺手进行格式化扫描,不能加入无关的依赖升级,也不能因为附近的代码看起来奇怪就尝试“清理”它。生成的变更往往显得很有把握,评审者容易漏掉夹在合理补丁中的无关修改。

在工作开始前设置变更预算。预算可以限制修改的文件数量、总行数,或请求组件之外的路径。预算本身不是风险评估,而是一个触发器,告诉代理停止并请求新的任务,避免把一次小修复悄悄变成全仓库重写。

创建拉取请求需要经过检查的 API 事务

HTTP 请求返回成功,并不能证明代理创建的是正确的拉取请求。控制器必须在报告成功前,核对仓库、头部分支、基础分支、提交 SHA,以及返回的拉取请求标识符。

对于 GitHub 风格的 API,有意义的请求字段包括提议的标题、headbase、正文和草稿状态。具体端点会因代码托管平台而不同,但安全检查不会变:

{
  "title": "OPS-1842: retry transient payment gateway failures",
  "head": "agent/OPS-1842-retry-payment-7f3a",
  "base": "main",
  "body": "Task: OPS-1842\nBase commit: 4b2c...\nTests: unit payment retry suite\nLimits: no configuration changes",
  "draft": true
}

发送请求前,查询分支并确认它的顶端 SHA 等于本次运行记录的提交。收到响应后,再获取拉取请求,比较其中的 headbase 和状态是否与请求一致。记录平台提供的不可变拉取请求编号或节点标识,不要只记录 URL。

重试需要特别处理。网络超时会造成经典的重复拉取请求问题:服务器可能已经创建了拉取请求 418,但客户端没有收到响应,于是再次尝试。控制器中应保存幂等记录,包括任务 ID、仓库、分支、基础 SHA 和拉取请求编号。重试时,先查找已有分支和拉取请求,再发起创建调用。

不要把任务标题作为唯一的幂等值。像“更新生成的文档”这样的重复请求会与之前的运行冲突。运行标识应识别一次运行,而任务 ID 用来帮助人们关联相关工作。

草稿拉取请求适合作为代理工作的默认设置。它告诉评审者变更已经存在,但还没有通过创建者自己声明的完成门槛。只有在必需命令执行完毕且运行记录包含结果后,代理才能将拉取请求标记为准备评审。如果仓库不使用草稿,则应由可信控制器添加类似 agent-created 的标签,而不是使用代理生成的文本来完成这件事。

评审者分配必须遵循所有权和风险

第一位评审者应来自仓库的所有权规则,而不是让代理猜测谁比较了解相关内容。GitHub 的 CODEOWNERS 文档描述了一种文件到所有者的映射,可以根据修改路径请求评审。GitLab 提供类似的审批和代码所有者机制。这些文件适合作为路由数据,但除非分支保护或合并规则强制要求,否则它们不会自动让评审变成必需条件。

这个区别很重要。根据设置不同,仓库可以显示已请求代码所有者评审,却仍然允许没有其批准的合并。要把路由和强制执行视为两个独立控制。在信任策略前,用一个明确未授权的测试拉取请求检查两者。

应使用最终提交后的变更文件列表,而不是代理计划修改的路径。生成的补丁可能在运行后期才触及共享库、部署目录或迁移文件夹。计算评审者时必须看到实际发生的变更。

当任务涉及所有权规则过于宽泛或没有覆盖的区域时,加入一位负责人。这个人负责确认任务意图。代码所有者可以确认实现是否适合某个组件,负责人则可以确认请求的行为是否符合产品需要。不要因为差异跨越了多个边界就分配十几个人。过长的评审名单往往会带来一个熟悉的结果:每个人都以为其他人已经处理了最难的部分。

某些路径应强制走更严格的流程,例如数据库迁移、授权代码、构建和发布定义、依赖清单、基础设施配置以及生成的构件。正确的做法不一定是“阻止代理”,很多时候应该是“要求了解后果的负责人参与”。迁移可能通过单元测试,却仍然让回滚变得不可能。

只有在拉取请求创建后,才能通过 API 分配评审者,并且要核对最终分配结果。如果某个所有者群组无法接收请求,控制器应将拉取请求标记为阻塞,而不是悄悄换成随机开发者。静默回退会把所有权规则变成装饰品。

测试描述证据,审批决定是否接受

将仓库密钥留在代理之外
Sallyport 从加密保险库注入 HTTP 凭据,因此代理拿到的是结果,而不是仓库密钥。

代理应准确说明运行了什么、没有运行什么,以及原因。“测试通过”如果没有命令、退出状态和被测试的提交 SHA,就是没有价值的说法。除了拉取请求正文,还应在其他位置保存这些证据,因为代理之后可以修改正文。

每次运行都使用一份小型结构化报告:

{
  "run_id": "run_01J...",
  "repository": "acme/payments-api",
  "head_sha": "8c71...",
  "commands": [
    {"command": "npm test -- payment-retry", "exit_code": 0},
    {"command": "npm run lint", "exit_code": 0}
  ],
  "not_run": ["integration suite requires payment sandbox approval"]
}

当证据中的 SHA 与分支顶端不一致时,控制器应拒绝将拉取请求转为准备评审。这能捕捉到一个微妙却常见的流程:代理运行测试后,又做了一次“小修正”,随后没有重新运行任何测试就创建了拉取请求。

必需的状态检查应留在仓库一侧。代理不得拥有豁免检查、批准自己的拉取请求、修改分支保护或合并的权限。检查可以证明某个已知命令成功运行,却不能证明新的授权规则正确、需求被正确理解,或任务本来就应该在那个仓库中执行。

不要让生成的摘要替代差异评审。好的摘要能帮助评审者快速了解情况,但它们仍然是作者提出的说法。评审者需要查看实际文件变更、测试、相关问题上下文,以及有意省略的内容。

每个创建的变更都需要能经受事故调查的审计轨迹

离线验证记录
运行 sp audit verify,无需保险库密钥即可离线检查加密哈希链。

拉取请求 URL 不是审计记录。仓库迁移、分支删除、访问权限变化,或有人编辑描述后,URL 所能提供的信息都会消失。应保留追加式事件记录,让调查人员能够回答:谁发起了运行、哪个进程进行了每次调用、哪个仓库发生了变化,以及系统返回了什么。

至少记录以下字段:

  • 运行 ID 和原始任务或工单引用
  • 执行操作的机器人身份,以及经过身份验证的代理进程身份
  • 仓库、基础分支、基础 SHA、头部分支,以及每个创建的提交 SHA
  • API 请求类型、不可变的拉取请求标识符、时间戳和结果状态
  • 评审请求、批准、检查结果、关闭、合并或拒绝事件

默认情况下,不要把明文凭据、完整源文件或任意任务提示词放入审计日志。调查人员需要的是可靠的操作事实,而不是另一份失控的敏感材料副本。如果保留补丁内容,应记录内容摘要,并遵循正常的保留和访问规则。

活动日志和决策日志的区别值得保留。活动日志说明某个 API 调用创建了拉取请求 418。决策日志说明谁批准了该代理会话、谁修改了它的授权,以及谁撤销了会话。发生事故时,两者都很重要。你需要知道发生了什么,也需要知道当时为什么这个操作者拥有权限。

Sallyport 可以在加密、哈希链式审计日志的基础上,将代理会话以及单独的 HTTP 或 SSH 调用投影到日志中,而 sp audit verify 可以在离线状态下对密文检查哈希链。这适合一种设置:代理请求执行操作,但从未直接获得仓库凭据。

哈希链可以让后续修改变得可检测,但不能让一份薄弱的事件记录自动变得完整。在操作发生的边界记录仓库和提交身份。一份完美验证的“已发送 HTTP 请求”记录,仍然无法告诉你该请求是否创建了错误的拉取请求。

将凭据留在代理之外,并把创建与合并分开

代理绝不能在提示词、环境变量、工作区文件或工具输出中持有宽权限仓库令牌。令牌一旦进入这些上下文,就可能通过日志、Shell 历史、错误消息、复制的会话记录,或诱使代理打印令牌的指令泄露。事后脱敏无法可靠地让秘密重新回到控制之下。

使用操作网关或范围狭窄的控制器,接受明确的请求,例如在这个仓库中创建分支、将这些提交推送到允许的前缀、针对批准的基础分支创建草稿拉取请求,或请求这些评审者。网关负责注入凭据并返回结果,同时拒绝超出声明范围的调用。

创建权限和合并权限是两种不同的特权。能够创建拉取请求的服务可能被允许提出成千上万个糟糕的变更,而能够合并的服务则可以把一个糟糕的变更送入生产环境。不要为了让演示更顺畅就把两者合并。即使代理生成了完美补丁,也应由受保护的分支规则和负责任的人工来控制合并操作。

按运行授权也优于永久信任代理进程。代理工具可以启动子进程,被意外复用于其他任务,并在原本授权访问的任务结束后继续存活。为进程提供一个有边界的会话,记录审批,并让撤销立即生效。

故障演练能暴露政策文字隐藏的缺口

立即撤销一次运行
当拉取请求运行的行为开始异常时,立即结束已批准的代理会话。

在依赖自动创建拉取请求前,先进行一次受控故障演练。使用测试仓库或一次性分支策略,并给代理一个试图跨越每道边界的任务。重点是验证拒绝和记录行为,而不是欣赏顺利完成的演示。

先准备一个允许的仓库和一个禁止的仓库。确认控制器能在前者创建预期的草稿拉取请求,并在任何分支出现前拒绝后者。然后尝试向 main 推送、强制推送到 agent/ 分支,以及针对未经批准的发布分支创建拉取请求。检查仓库事件,不要只看控制器消息。

接着,模拟创建请求到达仓库 API 后发生超时。重新启动运行,确认它找到原来的拉取请求,而不是再创建一个。记录测试结果后修改分支,验证准备评审的转换会因 SHA 不匹配而停止。

最后,在运行过程中撤销代理会话,再尝试进行一次 API 调用。调用应失败,拒绝应出现在决策记录中,并且代理输出中不能出现任何凭据。如果这些测试有任何一步依赖人工注意到聊天消息,那么这项控制实际上还不存在。

最实际的第一步,是盘点代理当前使用的令牌。列出它可以接触的所有仓库、可以更新的所有分支,以及它是否能够合并或修改设置。大多数团队都会发现令牌范围大于任务本身。让代理再创建一个拉取请求之前,先收窄权限。

常见问题

AI 代理需要哪些权限才能创建拉取请求?

代理应使用专用机器人身份,只拥有创建分支、向允许的命名空间推送代码和创建拉取请求所需的仓库及 API 权限。它不应获得维护者、管理或合并权限。凭据边界和提示词同样重要。

是否应该允许 AI 代理直接推送到 main 分支?

为代理设置类似 agent/ 的分支前缀,并拒绝它向其他位置推送。保护默认分支和发布分支,让它们只能通过正常的合并流程更新。这样可以防止代理选择方便的目标来绕过评审。

AI 生成的拉取请求应该多大?

拉取请求应围绕一个明确目的展开,差异范围要小,并附有清晰的测试。如果任务还需要无关的重构、依赖升级和行为变更,就把它拆开。让评审者同时还原多个决策时,评审质量会明显下降。

如何为 AI 创建的拉取请求分配评审者?

先根据仓库的所有权规则分配评审者,再为涉及生成文件、迁移、权限、部署定义或安全敏感代码的变更加上负责人。CODEOWNERS 适合做路由,但不能替代评审要求。必需的评审者必须由分支保护或合并规则强制指定。

代理创建拉取请求时应该记录什么?

记录代理会话、操作者身份、仓库、提交 SHA、分支名称、请求参数、拉取请求 URL、时间戳、审批事件和合并结果。如果保留策略允许,也应保存原始任务引用和补丁摘要。只有标题和 URL 不能构成可用的审计记录。

让 AI 代理自动创建拉取请求安全吗?

只要代理没有合并权限,只能在允许的仓库中工作,也不能向受保护分支推送,这种做法可以是安全的。必须要求人工评审、检查通过,并为每项操作保留清晰记录。把代理当成一个动作很快但不受信任的贡献者,而不是维护者。

AI 代理可以通过 API 创建拉取请求吗?

使用仓库平台的 API,从明确的基础提交创建分支,推送提交,再向批准的基础分支创建拉取请求。检查响应,记录返回的标识符,任何请求失败时都应停止。不要抓取网页界面,也不要仅凭生成的 URL 推断操作成功。

代理应该更新现有拉取请求,还是创建新的拉取请求?

当预期结果发生变化、原分支已被替代,或评审者需要做出独立决策时,代理应创建新的拉取请求。如果任务和所有权仍然相同,才应更新现有拉取请求。用复用拉取请求的方式隐藏新的工作范围,会破坏评审。

如何防止代理创建重复的拉取请求?

在自己的控制器中使用幂等数据:记录任务 ID、仓库、基础 SHA、分支名称和已创建的拉取请求编号,然后再重试。重试时,先查询分支和已有的未关闭拉取请求,再创建新的请求。大多数仓库 API 都会接受重复请求,但这不代表重复拉取请求没有危害。

测试通过后,AI 拉取请求就可以安全合并了吗?

不能。差异干净并不能说明代理选择了正确的行为、修改了正确的仓库,或使用了获批的依赖。测试和评审规则必须继续强制执行,人工评审者也需要足够的上下文来判断所请求的变更。

Sallyport

Sallyport 替你的 AI 智能体执行 API 调用和 SSH 命令。密钥留在你 Mac 上的本地密钥库里;每次运行由你批准,每个操作都落入一份密封的审计日志。

© 2026 Sallyport · 依据 Apache-2.0 开源 · Oleg Sotnikov