GitHub 工作流中 GITHUB_TOKEN 的最小权限实践

1 人参与

安全类工作流在代码审查中天然享有信任优势:职责正当,输出形式又常是报告或检查结论,审查者很容易把它归类为只读的观察者。但 GitHub 的运行时模型并不区分“审计者”与“操作者”。官方文档明确指出,工作流中任何一个第三方 action 被攻陷都可能造成显著影响,因为它会获得仓库上配置的全部 secrets,并可能使用 GITHUB_TOKEN 向仓库写入。名分上的只读职责,与运行时的实际权限之间,没有任何强制对应关系。

因此,讨论 GITHUB_TOKEN 的最小权限,不能只停留在“少给写权限”这句口号上,而要沿着它实际可触达的路径逐层收窄。

先看触发条件,再看权限

触发条件决定了谁能启动这条工作流。长期使用 pull_request_target、issues 或 schedule 的工作流,会在拉取请求、议题事件或定时器到点时自动运行,外部输入与仓库凭据落在同一次执行里。这里需要回答两个问题:这个触发方式是否必须存在?触发后运行的内容是否包含不可信的第三方代码或用户输入?如果某项审计只在发布前需要跑,它就不必在每次外部 PR 上被唤醒。

随后才是 permissions 块。GitHub 建议把 GITHUB_TOKEN 的默认权限设为只读仓库内容,再按单个 job 的需要提升。审查时应逐条核对工作流是否显式声明了 contents: write、issues: write、pull-requests: write、actions: write,并确认写权限没有落在只做读取与分析的任务上。若确实必须写入,也只应授予那个需要写入的 job,而不是整份工作流。

第三层与第四层

secret 的触达范围同样需要对照。仓库中任何具有写访问权限的用户都能读取仓库配置的所有 secret,所以工作流实际可触达的凭据面,就是 secret 面板的清单。可以逐项追问:这些 secret 是否真的需要?能否用 OIDC 换取短期云凭据,从而去掉长期密钥?敏感值是否被单独设置为 secret 而非明文写入工作流文件?GitHub 特别提示,不要用 JSON、XML 或 YAML 封装密码值,因为脱敏依赖对具体值的完全匹配,结构化封装会降低正确编校的概率。

最后是第三方 action 的引用方式。GitHub 文档给出的结论很直接:将 action 固定到全长度提交 SHA,是目前把 action 当作不可变发布使用的唯一方式;固定到特定 SHA 有助于降低攻击者在 action 仓库中添加后门的风险。固定时应选择 action 官方仓库而不是 fork 中的 SHA。使用 @v3、@main 这类浮动标签时,标签背后的代码可以在不改变工作流文件的情况下被替换,审查记录因此失去意义。

有一个容易被忽略的代价:把浮动标签替换成一串哈希,会让部分团队的 Dependabot 告警静默,因为文档说明 Dependabot 仅为使用语义版本控制的 action 创建告警,不会为固定到 SHA 的 action 创建告警。固定值需要配合依赖项审查或定期人工复核,才能不过时。

判定可疑看组合,处置看污染面

单看“有写权限”或“用了第三方 action”,都不足以定论。更可靠的判断是组合:自动触发 + 宽写权限 + 浮动的第三方引用 + 可触达的高价值 secret。任一组合成立,就应先隔离工作流——临时禁用,或移除 on 块中的高风险触发——再进入处置。

处置不是删文件了事。确认恶意的工作流应删除,或在保留审计功能的前提下重写为最小权限、SHA 固定的版本;运行日志中出现未脱敏的 secret 时,按文档建议删除日志并轮换。实践上,所有可能被该工作流接触过的凭据都应轮换,包括仓库 secret、组织级 secret 以及可能受写权限影响的发布凭据,并复核旧值确已失效。回归验证不只看“跑成功”,还要看“用不到的能力确实用不上”。

这套动作的价值在于把“一条工作流实际上能做什么”变成可追问、可复核、可回退的问题。平台权限语义与默认设置会随版本演进,落地前仍应以 GitHub 官方文档为准,并在授权环境中先验证再扩大范围。

参与讨论

1 条评论
  • 小猴子猴猴

    固定到SHA确实更稳妥,就是维护起来有点麻烦。