首页 / 资讯中心 / 文章详情

GitHub PAT 用 classic 还是 fine-grained:一份给 Bot 账号和 CI 的凭据清单

GitHub PAT 用 classic 还是 fine-grained:一份给 Bot 账号和 CI 的凭据清单 ★ FEATURED ARTICLE
2025 年 3 月有个叫tj-actions/changed-files的 GitHub Action 被投毒了。这个 Action 被大约 23000 个仓库使用。恶意代码做的事情不复杂把 runner 内存里的 CI/CD secrets 打到 workflow 日志里。后续统计有 218 个仓库在事件中泄露了凭据。调查往下追的时候发现源头不在 tj-actions 本身而在上游reviewdog/action-setup。而让这次攻击真正扩大的是一件事tj-actions 那个 bot 账号的 PAT 提供了对该 Action 仓库的写权限。一个可复用的 bot PAT把一次被攻陷的 workflow 依赖转换成了对另一个被广泛信任的依赖的写权限。由此我们来讲讲GitHub 的个人访问令牌该怎么选、怎么配、什么时候必须换掉。PAT 是什么性质的东西个人访问令牌是一个bearer credential。这个词的意思是持有即足够。用它的时候不需要密码不需要账号的第二重验证不需要任何交互式确认。拿到那串字符就等于拿到了权限。在开发者自己的终端里这已经够敏感了。放到一个 bot 账号上它能更新共享的 GitHub Actions、移动 tag、给几十个仓库写代码——这时候这枚 token 就已经是软件供应链的一部分了。所以评估 PAT 的时候问的不是它安全吗而是**“它泄了之后能碰到多少东西”**。Classic 和 fine-grained 的实际差别GitHub 现在有两类 PAT差别比大多数文档说的要大。Classic PAT用粗粒度 scope 描述权限。最典型的是repo——勾上这一项就是当前账号能访问的所有仓库的完整读写没法按仓库细分。它的默认行为有两个坑**不强制过期。**官方文档建议你设过期时间但系统不要求。只有整整一年完全没被用过GitHub 才会自动移除它。而任何一个周期性跑的 job 都能让一枚被遗忘的 token 无限期保持活跃。**权限范围跟着人走。**一个带reposcope 的 classic PAT 继承的是这个账号能触及的所有仓库和组织约束它的只有那几个粗粒度的 scope 和组织策略。Fine-grained PAT能限定到具体资源指定 resource owner个人或单个组织指定具体哪几个仓库指定每类操作的具体权限项比如只对某个仓库给 Contents: Read and write可以被组织管理员审批后才生效——开发者申请访问某个私有仓库管理员批准才管用另外有个容易忽略的组织层面 fine-grained PAT 的默认最长生命周期是366 天。个人账号下的 fine-grained token 可以设为无过期组织管理员也可以改这个策略。换句话说fine-grained 不等于自动会过期但它把爆炸半径从一个账号收窄到了几个仓库几个权限项。什么时候用哪个我的判断标准比较简单。用 classic 的场景个人小项目、一次性脚本、临时迁移。两三个 scope 两分钟搞定没必要上细粒度。必须 fine-grained 的场景公司项目、多人协作仓库、任何接触敏感代码的情况以及所有的 bot 账号和 CI 场景。第三条要加粗任何 token 挂在自动化流程里就应该用 fine-grained并且把仓库范围和权限项都收到最窄。有两个细节很实用往.github/workflows/目录推文件时classic token 需要额外勾选workflowscopefine-grained 需要Workflows: Read and write。很多人配完发现 workflow 文件推不上去就是卡在这。**组织层面可以强制修这个事。**Owner 能直接封禁 classic PAT或者给 classic 和 fine-grained 都设最长生命周期。不符合策略的存量 token 在使用访问组织资源时会被拦住——注意是访问组织资源时被拦不是被静默吊销这个区别很重要因为它意味着那些 token 在其他地方可能还活着。Bot 账号的 PAT 为什么比泄露密码更糟对比一枚泄露的密码一枚 bot PAT 麻烦在五个地方**没有交互式 MFA 挑战。**API 调用只需要那串字符不需要密码和第二重验证。你为账号配的一切 2FA 在这条路径上都不生效。**自动化会把恶意流量伪装成正常行为。**tag 更新、workflow 运行、仓库写入——这些本来就天天发生。那时候异常流量混在里面并不明显。**归属人不明确。**这枚 token 可能属于一个共享的 bot、一个已经离职的维护者或者三年前随手搭了这条流水线的人。出事的时候没人认领。**爆炸半径跟着人的账号走。**classic PAT 继承账号能触及的所有仓库和组织。**轮换在工程上有心理障碍。**因为没人能列出全部的使用方团队会推迟吊销理由是万一换坏了发布流程怎么办。这个万一会让 token 一直挂着。还有一条泄露路径不在 CI 配置里Unit 42 的研究发现过一个叫 ArtiPACKED 的问题一些大型开源项目的公开 Actions artifacts 里能直接翻出可用的 GitHub token 和第三方服务凭据。常见成因有两个上传了整个 checkout 目录凭据留在.git里上传了 linter 日志日志里有环境变量而 artifacts最长可以保留 90 天可下载。这条路径的特点是你的 CI 配置写得再规范也没用因为泄露不在配置层在什么东西被打进了产物这一层。建议加一条自查workflow 里上传 artifacts 的地方明确只用actions/upload-artifact指定具体路径不要图省事把整个目录传上去。现在就该做的五件事第一把组织里的 classic PAT 列出来。Settings → Developer settings → Personal access tokens组织视角能看到组织成员创建的 token 概览。重点找两类没设过期时间的以及超过一年没动但最近又活跃的后者通常意味着有人捡起来用了。第二给 bot 账号换 fine-grained。把 PAT 换成限定到具体仓库、具体权限项的 fine-grained token。如果场景允许更进一步的方案是换成GitHub App——它有独立的身份、细粒度权限、自动过期的安装令牌不依附于任何人的账号。Bot 账号最不该出现的东西就是挂在某个人账号下的长期 token。第三设过期策略。组织 Owner 在 Settings → Personal access tokens 那里可以封禁 classic PAT、给两类 token 设最长生命周期。设完之后那些不合策略的存量 token 会被拦。别一次性开到最严。先设一个宽松的值跑两周看哪些流程断了收紧再迭代——这样阻力最小。第四检查 workflow 里有没有 scope 缺口。推 workflow 文件需要workflowscope 或Workflows: Read and write。已经配好的 token 如果没这两项会在某次改 CI 配置的时候突然失败报错信息通常很不直观。第五明确 rotations 的责任人。每次轮换都得有人负责验证没跑坏的东西。没有责任人团队就会一直推迟。说两件容易被混为一谈的事第一token 保护和账号登录保护是两个层面。前面说的所有东西——PAT、GitHub App、SSO——都属于机器凭据。它们和你登录 GitHub 网页时用的第二重验证完全不是一回事。你给账号开了 TOTP不代表那些 PAT 就安全了。用户登录那一层的兜底还是那三件事恢复码单独存一份不要只放手机里、验证器的迁移路径要提前确认、换机之后先真实登录再清理旧设备。第二TOTP 验证器不替代 PAT 治理。这是个需要明确说出来的边界。验证器管的是登录环节的第二重验证它不管PAT、SSH Key、OAuth 这类机器凭据。tj-actions 那次事件里tj-actionsbot 的 2FA 是不是开着并不重要因为攻击走的根本不是登录那条路——它走的是 runner 内存里那些现成的凭据。把它们当成一件事就会出现我明明开了 2FA怎么 CI 还是被打穿了这种困惑。两套东西分别处理。
阅读完成 · 觉得有帮助?
咨询建站