ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

给 AI Agent 最小权限:git-ai 安全模型——Token 身份、RPC 白名单与分支策略

给 AI Agent 最小权限:git-ai 安全模型——Token 身份、RPC 白名单与分支策略

【免费下载链接】git-ai

Git designed for AI agents.

项目地址:https://gitcode.com/gh_mirrors/op/git-ai
点击查看免费下载

当 AI Agent 开始自动提交代码,如何保证它不会写坏主分支、更不会把仓库拖下水?git-ai 项目(Graft,一款"为 AI Agent 设计的 Git")给出的答案是完整的最小权限安全模型:每个 Agent 持有独立的 Token 身份,所有 gRPC 请求受RPC 白名单约束,每一次分支更新再经过分支策略裁决。三层防线层层收紧,让 Agent"能干完活,但动不了不该动的东西"。

这篇文章带你从零看懂这套机制:为什么 Agent 需要最小权限、Token 身份如何防冒充、白名单里有什么和绝不能有什么、分支策略规则怎么写,以及上线前该记住的 6 步清单。

为什么 AI Agent 必须用最小权限 🤔

Agent 是"自动化写作者"——它的价值就在于无需人工干预就能落地变更,这同时意味着风险:

  • 如果 Agent 能创建/删除仓库,它可以安装服务端钩子,甚至把仓库内容推送到任意外部地址(数据外泄);
  • 如果 Agent 能随意更新引用,它可以绕过保护分支直接改写main;
  • 如果请求体里的用户名可以伪装身份,Agent 可以"冒充"别的 Agent 给自己批审核。

git-ai 的安全模型针对这三类风险设计了三道闸门:

防线解决的问题关键配置
① Token 身份你是谁?能否冒充别人?[[graft.agent]]
② RPC 白名单你能调用哪些接口?agent_rpcs
③ 分支策略你能改哪些分支?[[graft.policy]]

完整定义见官方文档 AGENT-SECURITY.md。

Token 身份:每个 Agent 一张独立"工牌" 🔑

给每个 Agent 发独立 Token

每个 Agent 在配置文件中占一个[[graft.agent]]条目,用token_file指向自己的密钥文件(或直接用token字段):

[[graft.agent]] name = "planner" token_file = "/etc/graft/tokens/planner" [[graft.agent]] name = "builder" token_file = "/etc/graft/tokens/builder"

启动时服务器会严格校验配置:名字为空、重名、两个 Agent 共用一个 Token、Agent Token 与共享auth.token相同,都会直接拒绝启动。注释完整的示例配置在 examples/graft/config.toml。

认证细节:常量时间比较 + HMAC 时效窗口

认证逻辑在 auth/token.go 与 internal/gitaly/server/auth/agent.go 中实现,有两处值得新手注意:

  • 常量时间比较:Token 比对使用crypto/subtle,防止攻击者通过响应耗时差异猜测密钥;
  • v2 HMAC 格式:除明文 bearer 外,还接受用 Agent Token 作为密钥计算的 HMAC-SHA256 签名 Token,带时间戳且只有约 30 秒的有效期窗口——重放旧 Token 无效。

身份只来自 Token,不来自请求体

这是最关键的设计:身份取自 Token,绝不取自请求。

Agent 请求里携带的用户 ID 字段(gl_id、gl_username)会被服务器统一覆写为 Token 对应的 Agent 名。也就是说,builder拿着自己的 Token、却在请求里自称planner去更新main,依然会被当成builder来裁决——直接拒绝。Agent 身份通过 internal/graft/identity/identity.go 中的Identity结构在请求全链路传递,changelog、策略裁决、审核记录用的都是这个身份。

⚠️ 共享auth.token是运维凭证,不是 Agent。它保留给备份工具、校验器等管理端组件,绝不能发给 Agent 使用。

RPC 白名单:Agent 能调什么,绝不能调什么 🛡️

通过 Token 认证的 Agent,下一步还要过 RPC 白名单。白名单定义在 internal/gitaly/server/auth/agent_methods.go,分三层理解:

内置允许:恰好够 Agent 干活的接口

内置白名单正是 SDK、graftCLI 和缓存层实际用到的最小集合:

  • 读类接口:对象/树读取(ObjectService)、变更集(ChangesetService,只读)、派生数据、代码搜索(SearchService);
  • 只读访问器:commit/ref/blob/diff 等服务的Find*、List*、Get*方法;
  • 审核:ReviewService全部接口——它的写操作只写审核存储、绝不碰引用,且记录的是 Token 身份,Agent 无法"替别的 Agent 批审核";
  • 落地与提交:UserLand、UserCommitFiles、UserCreateBranch、UserDeleteBranch,且全部要过分支策略;
  • 杂项:ServerInfo、RepositoryExists与健康检查。

永久禁止:连配置也开不了的"红线"

有一批接口对 Agent永远关闭,即使你写进agent_rpcs也不行,启动时会被直接报错拦下:

禁止的接口为什么危险
CreateRepository、CreateRepositoryFromURL/Snapshot等可从任意 URL 拉数据建库,快照甚至可携带钩子文件,构成服务端请求伪造与钩子植入
BackupRepository、RemoveRepository、ReplicateRepository可把仓库数据写到调用者指定的任意位置(外泄)
UpdateRemoteMirror、FindRemoteRepository向调用者自选的 URL 推数据/发请求(外泄与 SSRF)
UpdateReferences、DeleteRefs、WriteRef绕开策略直接写引用
HookService、CleanupService、PartitionService等伪造钩子回调、破坏存储结构等管理操作

代码中对每条红线都写了注释说明攻击场景,可以对照 agent_methods.go 阅读。

默认拒绝 + 可控扩展

白名单之外的接口默认全部拒绝,错误码PermissionDenied,且信息不泄露更多细节。如果确实需要额外接口(比如查仓库大小),可以用全限定 gRPC 方法名扩展:

[graft] agent_rpcs = ["/gitaly.RepositoryService/RepositorySize"]

服务器启动时逐条校验:方法未注册、或属于红线清单,都会拒绝启动而不是静默忽略。值得一提的是,SSHService也不在内置白名单里——Agent 默认无法通过 Git 协议 fetch/push,这堵住了绕过UserLand冲突处理直接推分支的口子。

两类"夹带私货"的请求也会被拒

白名单只管"调哪个方法",还有两个请求级检查专门防绕过:

  • git_config_options一律拒收:Git 会应用客户端传入的配置,Agent 可以借此覆写core.hooksPath跳过访问检查,甚至用uploadpack.packObjectsHook执行任意命令;
  • gitaly-servers元数据一律拒收:它指定服务器地址与 Token,Agent 可借它让服务器连接任意内网地址。

分支策略:用几行 TOML 决定谁能动哪个分支 🌿

前两层管"你是谁、能调什么",最后一层管"能改什么"。策略规则写在[[graft.policy]](定义见 CONFIGURATION.md),每个规则只有三个字段:

  • actor:执行者 glob,匹配 Token 对应的 Agent 名;
  • refs:引用 glob 列表,refs/heads/**可匹配任意深度;
  • allow:是否放行。

规则按顺序求值:第一条"actor 匹配且 refs 全部匹配"的规则说了算;没有任何规则命中的引用 = 拒绝;一次更新触及多个规则管辖的引用时,必须全部放行才算数。保护main的推荐写法(与 examples/graft/config.toml 一致):

# 1. planner 可以更新 main [[graft.policy]] actor = "planner" refs = ["refs/heads/main"] allow = true # 2. 其他人不能碰 main [[graft.policy]] refs = ["refs/heads/main"] allow = false # 3. 其余分支对所有认证者开放 [[graft.policy]] refs = ["refs/heads/**"] allow = true

两个新手容易忽略的细节:

  • HEAD也在管辖范围内:策略覆盖HEAD符号引用,Agent 无法通过"偷偷把HEAD指到别处"绕过保护分支;
  • 策略对 Agent 的每一次引用更新生效,不止UserLand:pre-receive 钩子检查推送与 OperationService 调用,reference-transaction 钩子还会在引用加锁、变更之前再查一遍,所以没有 pre-receive 的 RPC 也逃不掉。被拒的更新整体中止、不写入 changelog,服务器日志会记下是哪条规则拒绝的。边界情况汇总见 LIMITATIONS.md。

审计追溯:谁动过仓库,一条日志说清楚 🔍

最小权限的最后一块拼图是"事后可查"。Agent 的每次变更都会写入变更集日志,记录的是Token 对应的身份而非请求自称的用户,用graft log即可逐条查看操作者。认证失败也会被 Prometheus 指标(gitaly_authentication_errors_total)分类计数——"HMAC 签名错误""Token 过期"各有独立标签,异常调用一查便知。

请求在服务器内部的完整路径同样可观测:下图是一次 gRPC 请求经过各服务与拦截器的调用链路,认证与安全检查正是发生在这一层。

如果搭建了 ELK 日志栈,还可以按字段过滤 Agent 请求、认证错误与钩子回调记录,把安全事件从日志里"捞"出来复核。

新手上线清单:6 步给 Agent 配好最小权限 📋

源自官方 AGENT-SECURITY.md 的运维检查清单,照着做即可:

  1. 一 Agent 一 Token:每个 Agent 独立token_file,文件权限只对 Agent 自身账号可读;
  2. 共享auth.token只留给自己:交给运维与工具,绝不发给 Agent;
  3. 保护关键分支:用[[graft.policy]]锁住默认分支与 release 分支,其他分支放开;
  4. 自动解冲突按需开启:[graft.land.resolver]默认关闭,只对你信任的 actor 和路径启用;
  5. 隔离特权组件:graft-resolve与校验器尽量放在独立账号上(它们用运维凭证而非 Agent 凭证写入);
  6. 定期审计:用graft log检查变更集日志,每条记录都带着操作者身份。

小结

git-ai 的安全模型可以浓缩成一句话:身份来自 Token,接口受白名单,分支看策略。三层都是"默认拒绝、显式放行",配置错误宁可拒绝启动也不静默降级,红线接口连配置都无法打开。对新手来说,只要记住 Token 三不(不共用、不发给 Agent 共享 Token、不在请求体里自称身份)、白名单两禁(git_config_options、gitaly-servers)、策略一原则(没规则命中就是拒绝),就能为自动化流水线配出一套既好用又安全的 AI Agent 环境。

【免费下载链接】git-ai

Git designed for AI agents.

项目地址:https://gitcode.com/gh_mirrors/op/git-ai
点击查看免费下载
上一篇:.NET Windows Desktop Runtime:桌面应用开发的革命性解决方案
下一篇:如何轻松搞定AutoHotkey版本升级:一键完成脚本转换的终极方案

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进