Actions 的安全基线不是“仓库私有就行”,而是每次运行拿到最小权限、密钥不进日志、云端访问尽量用短时凭证。
- 01先读摘要,判断是否与你的场景相关。
- 02再看来源,保留继续查证的路径。
- 03最后看步骤、风险和可复用动作。
GitHub 官方安全参考反复强调同一句话:任何有仓库写权限的人都能读取仓库里的 Secrets,因此工作流里的凭证必须是最小权限。GITHUB_TOKEN 默认权限过高、Secrets 放在不相关环境、云端密钥长期存在,是 CI/CD 最常见的三类风险。下面这套清单按仓库、工作流、环境和 Runner 四层收紧。
适用场景
- 仓库使用 GitHub Actions 做测试、构建、部署或自动化任务。
- 工作流需要访问云平台、包仓库或其他外部服务。
- 你想把 GITHUB_TOKEN 从全写权限降到只读。
- 你正在从长期云密钥迁移到 OIDC 短时凭证。
不适用场景
- 没有 GitHub Actions,也没有 CI/CD 工作流。
- 团队完全使用 GitHub-hosted runner,并且不需要访问云资源。
- 你希望一个 token 同时满足所有历史工作流,不做逐项排查。
- 你把 Actions 安全当成仓库私有就能解决,不检查第三方 action。
准备材料
- GitHub 仓库、组织或企业管理权限。
- 当前 workflow 文件列表和使用的第三方 action。
- 云平台或外部服务的账号权限,用于配置 OIDC 信任。
- Secrets 清单,记录名称、使用环境、最后修改人和轮换周期。
- 一个测试分支,用于灰度权限变更。
步骤一:把默认 GITHUB_TOKEN 权限设为只读
GitHub 官方建议把仓库或组织的默认 token 权限设为 read access only for repository contents,需要时再在单个 job 里增加。这样即使某个 action 被滥用,token 也无法直接写仓库。
- 在仓库 Settings 的 Actions 页面找到 Workflow permissions。
- 选择 Read repository contents and packages permissions。
- 组织层面也设置默认只读,避免新仓库继承高权限。
- 记录哪些工作流需要 write,再在 job 内显式开放。
步骤二:在每个 job 里显式声明 permissions
不要在 workflow 顶部一次性放开所有权限。官方文档建议在需要 token 的 job 中设置最小 permissions。下面是一个只读示例:
permissions:
contents: read
id-token: write
id-token: write 只在需要 OIDC 时出现。它允许请求 OIDC JWT,但不会自动给仓库写权限。发布 job 如果确实需要创建 release,再单独把 contents 设为 write,不要复制给测试 job。
步骤三:把 Secrets 放进对应 Environments
仓库级 Secrets 对所有 Actions 可见,环境级 Secrets 只对指定 environment 可见。官方文档说明环境中可以配置 required reviewers,让生产部署前有人工审批。把测试密钥和生产密钥分开,能显著减少误用。
- 在仓库 Settings 的 Environments 创建 production、staging、test。
- 把生产云密钥放入 production environment,而不是仓库 Secrets。
- 为 production 配置 required reviewers 和 deployment branch。
- 在 workflow 的 job 中声明 environment,并引用 secrets.ENV_NAME。
- 定期检查哪些 workflow 能访问生产环境。
步骤四:访问云资源时优先使用 OIDC
GitHub 官方说明 OIDC 允许工作流从云平台获取短时 token,不再把长期云凭证存成 GitHub Secrets。配置时必须在云角色上设置至少一个 subject 或 audience 条件,否则不受信任仓库也可能请求访问。
- 为每个仓库或工作流创建独立云角色。
- 在角色信任策略中限制 repo、environment 和 ref。
- 不要使用宽泛的 “所有 GitHub Actions” 信任条件。
- 用官方 login action 或 getIDToken 请求 token。
- 上线后验证 token 过期时间和最小权限。
步骤五:给自托管 Runner 画清边界
GitHub 官方强烈建议公开仓库不要使用自托管 runner,因为任何能开 PR 的人可能获得 runner 环境,包括 Secrets 和 GITHUB_TOKEN。私有仓库也要限制到可信任用户,并使用隔离环境、只读根文件系统和受控网络。
- 不在公开仓库配置自托管 runner。
- runner 标签区分生产、测试和不同项目。
- 环境密钥只暴露给对应 environment 和 runner group。
- 定期清理陈旧 runner,检查最后心跳。
- 需要执行任意代码的 runner 与主开发机隔离。
步骤六:审计第三方 action 和凭证轮换
第三方 action 可以通过 github.token 间接访问仓库,即使 workflow 没有显式传 token。官方建议固定 action 版本或使用 SHA,并对第三方 action 做代码审查。同时为仍存在的长期 Secrets 设置轮换日期,而不是无限保留。
- 把 action 从 main 改为具体 tag 或 commit SHA。
- 检查 action 是否读取 inputs、token 和环境变量。
- 删除不再使用的 Secrets,避免旧 token 长期有效。
- 每季度导出 Secrets 使用情况,标记 90 天未使用的项目。
- 需要长期凭证时使用最小 scope 的 fine-grained token,不用经典 token。
可复制检查表
仓库:
默认 token 权限:
job 名:
permissions:
环境:
Secrets 名称:
OIDC 角色:
Runner 标签:
action 版本:
最近审计日期:
下次轮换:
实际例子:部署工作流从全写 token 改成分层权限
一个小型项目原先在 workflow 顶部写 permissions: write-all,并把 AWS 长期密钥放进仓库 Secrets。审计后改为:测试 job 只读 contents,生产部署 job 声明 environment production,使用 aws-actions/configure-aws-credentials 配合 OIDC,并设置 required reviewers。两周后,某个第三方 action 被替换时不再能访问生产密钥,部署日志也变得更干净。
验收清单
- 仓库和组织默认 token 权限为只读。
- 每个 job 都显式声明所需权限。
- 生产 Secrets 只存在于对应 Environment。
- 云访问使用 OIDC,且信任条件按仓库或环境限制。
- 公开仓库没有自托管 runner。
- 第三方 action 固定版本或 SHA。
- Secrets 有负责人和轮换日期。
常见坑
- 在 workflow 顶部写 permissions: write-all,测试 job 也有写权限。
- 把生产密钥放仓库级 Secrets,任何 workflow 都能读。
- OIDC 信任条件写成所有 repo,风险反而扩大。
- 公开仓库使用自托管 runner,接受不可信代码执行。
- 依赖 main 分支的 action,更新后没有审查。
排错路径
- job 报 403:检查 GITHUB_TOKEN permissions 是否缺所需 scope。
- OIDC token 无法获取:确认 job 有 id-token: write,并检查云角色信任条件。
- Environment 密钥不可见:确认 job 声明了对应 environment。
- required reviewers 不触发:检查 environment protection rule 和 deployment branch。
- 自托管 runner 被滥用:立即移除公开仓库 runner,并轮换可能暴露的 Secrets。
后续维护建议
更新日期:2026-08-11。建议每季度检查一次 workflow permissions、Environment 列表、OIDC 角色和第三方 action。每次新增部署能力时,先在测试环境验证最小权限,再复制到生产。长期 Secrets 能迁移到 OIDC 就迁移,不能迁移时至少设置过期和轮换提醒。