GitHub Actions Secrets 管理:从泄露风险到 OIDC 无密钥部署
2025年3月的一个周末,GitHub 安全团队向超过 23,000 个仓库的所有者发送了一封紧急邮件。
他们的 secrets 可能已经泄露了。
罪魁祸首是一个叫 tj-actions/changed-files 的 action——这个被广泛使用的工具被攻击者入侵,悄悄窃取 workflow 中所有的环境变量和 secrets。你可能在想:我的项目也用了这个 action,我会中招吗?
说实话,这次事件把一个很多人忽略的问题摆到了台面上:GitHub Actions 里的 secrets,到底该怎么管?
这篇文章我们来聊聊三件事:secrets 的三层架构怎么选、8 条安全铁律怎么守、以及 OIDC 怎么让你彻底告别静态凭证泄露的噩梦。
一、GitHub Actions Secrets 的三层架构
GitHub 提供了三个层级来存储 secrets:Repository、Environment、Organization。选哪个?答案是:看你的场景。
Repository Secrets:个人项目首选
这是最简单的层级。secrets 存在仓库级别,所有 workflow 都能访问。如果你是个人项目、单体仓库、没有多环境部署需求——直接用这个就够了。
唯一的缺点:无法区分 staging 和 production 的同名 secret。比如你有个 DATABASE_URL,staging 和 production 的值不一样,怎么办?这就需要 Environment Secrets 了。
Environment Secrets:多环境部署必备
Environment secrets 可以按环境隔离,还支持审批流程。你可以在 GitHub 仓库设置里创建 staging 和 production 两个环境,分别配置不同的 secrets。
一个关键特性:只有引用该环境的 job 才能访问对应的 secrets。这让安全边界更清晰。
jobs:
deploy-staging:
runs-on: ubuntu-latest
environment: staging # 引用 staging 环境
steps:
- run: echo "Deploying to staging..."
- env:
API_KEY: ${{ secrets.API_KEY }} # 来自 staging 环境的 secret
deploy-production:
runs-on: ubuntu-latest
environment: production # 引用 production 环境(可配置审批)
steps:
- run: echo "Deploying to production..."
- env:
API_KEY: ${{ secrets.API_KEY }} # 来自 production 环境的 secret
在上面的配置里,deploy-staging 只能访问 staging 环境的 secrets,deploy-production 只能访问 production 环境的 secrets。两者互不干扰。
另外,Environment 还支持「保护规则」——比如 production 环境可以配置必须经过人工审批才能执行。这在团队协作时特别有用。
Organization Secrets:团队共享,统一管理
如果你的团队有几十个仓库,每个仓库都要配置相同的 AWS_ACCESS_KEY——复制粘贴几十次,更新时再改几十次,想想都头大。
Organization secrets 就是为了解决这个问题。在组织级别配置一次,所有仓库都能使用。你还可以控制哪些仓库能访问:所有仓库,或指定的仓库列表。
# 在仓库的 workflow 里,用法跟 repository secrets 完全一样
steps:
- env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
怎么选?
简单来说:
| 场景 | 推荐层级 |
|---|---|
| 个人项目、单一环境 | Repository Secrets |
| 多环境部署(staging/production) | Environment Secrets |
| 团队多仓库、共享凭证 | Organization Secrets |
从我踩坑的经验来看,很多项目一开始用 Repository Secrets,等到有多环境需求时才迁移到 Environment Secrets——迁移成本不高,但还是建议提前规划。
二、Secrets 安全最佳实践 — 8 条铁律
前面讲了 secrets 怎么存,这里说下怎么用。我总结了 8 条从实战中摸索出来的铁律,每条都有教训。
1. 命名要规范
全大写 + 下划线分隔,比如 AWS_ACCESS_KEY_ID。别用小写、别用驼峰。原因很简单:一眼就能看出这是个 secret,而且不会跟普通变量混淆。
2. 别把 JSON 存成单个 secret
这是个经典坑。有人把整个配置文件塞进一个 secret:
{"api_key": "xxx", "db_url": "yyy", "token": "zzz"}
然后用 fromJson 在 workflow 里解析。问题在于:一旦这个 secret 泄露,所有敏感信息一起泄露。正确做法是把每个值存成独立的 secret。
3. 显式传递,不要内联
# 错误示范 ❌
- run: my-cli --token ${{ secrets.MY_TOKEN }}
# 正确做法 ✅
- env:
MY_TOKEN: ${{ secrets.MY_TOKEN }}
run: my-cli --token $MY_TOKEN
为什么?GitGuardian 的研究表明,命令行参数可以被同一机器上的其他进程通过 ps x -w 看到。环境变量相对安全得多。
4. 定期轮换
30 到 90 天换一次。我知道轮换很烦——但比起泄露后的补救,这点麻烦真的不算什么。Blacksmith 团队建议,如果你用云服务(AWS/GCP),可以结合 OIDC 完全跳过这个步骤。
5. Pin Actions 到 SHA
供应链攻击的第一道防线。
# 错误示范 ❌
- uses: tj-actions/changed-files@v45
# 正确做法 ✅
- uses: tj-actions/changed-files@b827595e0a7e97537d7c7a2f458b5a8e6d5c8e39
用 commit SHA 而不是版本号标签。标签可以被攻击者篡改,SHA 是不可变的。
6. GITHUB_TOKEN 只给最小权限
GitHub 自动为每个 workflow 提供 GITHUB_TOKEN。默认权限太高了——能写代码、能改 issue。建议在 workflow 或仓库设置里把它改成 read-only:
permissions:
contents: read
7. 检查日志里 secrets 是否正确遮蔽
GitHub 会自动把 ${{ secrets.XXX }} 在日志里替换成 ***。但如果你这样写:
- run: echo "Token is $MY_TOKEN"
日志里就会出现真实的 token 值。记得测试一下你的 workflow,确认没有意外暴露。
8. 注册衍生敏感值
如果你的 workflow 从一个 secret 生成了新的敏感值(比如用 API key 生成 JWT),把这个新值也注册成 secret,不要只在内存里传递。
这 8 条铁律不是纸上谈兵——tj-actions 事件后,StepSecurity 团队对数千个公开仓库做了审计,发现违反这些规则的仓库占了相当比例。改起来不难,但需要花点时间逐项排查。
三、OIDC — 无密钥时代的云部署认证
前面两条铁律里提到了「定期轮换 secrets」。说实话,轮换这事真的很烦——每次都要手动改 AWS 控制台、更新 GitHub secrets、通知团队成员。
OIDC(OpenID Connect)提供了一条出路:干脆不存 secrets。
OIDC 怎么工作?
传统方式:你在 AWS 里创建一个 IAM 用户,生成 access key,把 key 存到 GitHub secrets。每次 workflow 运行时,用这个静态 key 去请求 AWS 资源。
OIDC 方式:GitHub 作为身份提供商,向 AWS 证明「这个 workflow 来自 eastondev/my-repo 仓库」。AWS 验证后,发放一个短期 JWT 令牌(有效期几分钟到几小时)。workflow 用这个临时令牌完成任务,令牌过期后自动失效。
不需要存储任何长期凭证,不需要轮换,不存在泄露风险。
AWS OIDC 配置示例
步骤分两部分:AWS 端配置信任关系,GitHub 端请求令牌。
AWS 端(控制台操作):
- 创建 IAM Identity Provider,URL 设为
https://token.actions.githubusercontent.com - 创建 IAM Role,信任策略限制为你的仓库:
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
"token.actions.githubusercontent.com:sub": "repo:eastondev/my-repo:ref:refs/heads/main"
}
}
}]
}
这个配置的意思是:只有 eastondev/my-repo 仓库的 main 分支可以 assume 这个 role。
GitHub 端(workflow 配置):
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
id-token: write # 必需:请求 OIDC token
contents: read
steps:
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789:role/GitHubActionsRole
aws-region: us-east-1
- run: aws s3 sync ./dist s3://my-bucket
注意到这里没有任何 secrets.AWS_ACCESS_KEY —— 直接用 role 认证。
GCP 和 Azure
三大云都支持 OIDC,配置大同小异:
| 云平台 | GitHub Action | 官方文档关键词 |
|---|---|---|
| AWS | aws-actions/configure-aws-credentials | OIDC federation |
| GCP | google-github-actions/auth | Workload Identity Federation |
| Azure | azure/login | Federated Identity Credentials |
OIDC 的一个意外好处
根据 johal.in 的测试,OIDC 认证延迟比传统 secrets 方式降低了约 87%。原因很简单:不需要从 GitHub secrets API 读取凭证,直接从本地 JWT 换取临时令牌。
从我个人的使用体验来看,OIDC 是云部署的首选方案。唯一的门槛是配置稍微复杂一点——但配好一次后,后面就彻底不用管了。
四、供应链攻击防护 — tj-actions 事件复盘
回到开头提到的 tj-actions/changed-files 事件。它是怎么发生的?我们能学到什么?
事件经过
2025年3月,攻击者获取了 tj-actions 仓库的 maintainer 权限(具体途径还在调查中,可能是凭证泄露或账户入侵)。他们在 v45 版本的代码里插入了一段恶意脚本,这段脚本会在 workflow 运行时悄悄读取所有环境变量和 secrets,然后发送到攻击者控制的服务器。
Semgrep 的分析显示,所有使用 tj-actions/changed-files@v45 的 workflow 都受影响——无论你用的是 GitHub secrets 还是 OIDC,只要这个 action 能访问到环境变量,就会被窃取。
Unit42 Palo Alto 的报告指出,超过 23,000 个仓库使用了这个 action。其中包括一些知名项目的 fork。
教训
这次事件暴露了几个问题:
- Action 版本号标签不可信——攻击者可以修改标签指向恶意 commit
- 第三方 action 能访问你的 secrets——一旦被入侵,所有 secrets 都暴露
- 历史运行日志可能泄露敏感信息——即使现在修复了,过去的运行记录里可能仍有痕迹
你的检查清单
如果你曾经使用过 tj-actions/changed-files,建议逐项排查:
□ 检查 workflow 日志,确认没有 secrets 泄露到输出
□ 轮换所有可能暴露的 secrets(API keys、tokens 等)
□ 将 action pin 到 commit SHA,而不是版本号
□ 审计其他第三方 action 的 maintainer 来源
□ 考虑用 Dependabot 或 Renovate 自动化 action 版本检查
对于还没被入侵的项目,Pin SHA 是最重要的防护措施。虽然 SHA 比版本号难读,但这是唯一能防止标签篡改的方式。
另外,GitHub 在 2026 安全路线图里提到一个重要方向:分离代码贡献权限与凭证管理权限。未来可能会引入更细粒度的访问控制,让第三方 action 只能访问必要的 secrets,而不是全部。这是个好消息——但目前我们还是要靠自己守好防线。
结论
GitHub Actions Secrets 管理不是复杂的技术问题,而是需要持续关注的安全实践。总结一下:
三层架构怎么选:个人项目用 Repository Secrets,多环境部署切换到 Environment Secrets,团队共享用 Organization Secrets。
安全铁律怎么守:Pin SHA、显式传递、定期轮换——这三条最重要。
云部署怎么认证:OIDC 是首选,无 secrets 存储、无轮换烦恼。
供应链攻击怎么防:限制第三方 action 的 secrets 访问,审计 maintainer 来源。
从 tj-actions 事件中吸取教训后,我自己的做法是:所有 action 都 pin 到 SHA,云部署全部用 OIDC,每个月做一次 secrets 审计。这套流程花了大概两周建立,但之后维护成本很低。
如果你还没开始做这些检查,建议今天就把这篇文章里的检查清单跑一遍。比起事后补救,提前预防的成本要低得多。
配置 GitHub Actions OIDC 无密钥部署
为 AWS 云服务配置 OIDC 认证,实现无需存储静态凭证的安全部署
⏱️ 预计耗时: 30 分钟
- 1
步骤1: 创建 IAM Identity Provider
在 AWS IAM 控制台创建身份提供商:
• Provider URL: https://token.actions.githubusercontent.com
• Audience: sts.amazonaws.com
• 记录生成的 Provider ARN - 2
步骤2: 创建 IAM Role 并配置信任策略
创建 IAM Role,信任策略限制为你的 GitHub 仓库:
```json
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::ACCOUNT_ID:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
"token.actions.githubusercontent.com:sub": "repo:OWNER/REPO:ref:refs/heads/main"
}
}
}]
}
```
• 替换 ACCOUNT_ID 为你的 AWS 账号 ID
• 替换 OWNER/REPO 为你的 GitHub 仓库
• 可移除 :ref:refs/heads/main 允许所有分支 - 3
步骤3: 配置 Workflow 使用 OIDC
在 GitHub Actions workflow 中请求 OIDC token:
```yaml
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
id-token: write # 必需:请求 OIDC token
contents: read
steps:
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::ACCOUNT_ID:role/GitHubActionsRole
aws-region: us-east-1
- run: aws s3 sync ./dist s3://my-bucket
```
• id-token: write 是必需的权限声明
• 无需配置任何 secrets.AWS_ACCESS_KEY_ID - 4
步骤4: 测试和验证
运行 workflow 验证 OIDC 配置:
• 检查 workflow 日志,确认认证成功
• 验证 role-to-assume 正确
• 确认无需任何静态凭证即可访问 AWS 资源
• 测试目标操作(如 S3 同步、ECR 推送等)
常见问题
Repository Secrets 和 Environment Secrets 有什么区别?
如何在 workflow 中安全地使用 secrets?
• 显式传递:通过 env 字段注入,不要内联使用 ${{ secrets.XXX }}
• 最小权限:只传递给需要的 step,使用 GITHUB_TOKEN 时设置 permissions: contents: read
• 定期轮换:建议 30-90 天轮换一次,云部署可使用 OIDC 完全跳过轮换
什么是 OIDC?为什么比传统 secrets 更安全?
如何防止供应链攻击(如 tj-actions 事件)?
• Pin Actions 到 commit SHA,不要用版本号标签
• 限制 secrets 仅传递给信任的 actions
• 审计第三方 action 的 maintainer 来源
• 定期运行 Dependabot 或 Renovate 检查版本更新
secrets 会在日志中泄露吗?
Organization Secrets 适合什么场景?
11 分钟阅读 · 发布于: 2026年4月18日 · 修改于: 2026年4月20日
相关文章
GitHub Actions Matrix 矩阵构建:多版本并行测试实战
GitHub Actions Matrix 矩阵构建:多版本并行测试实战
GitHub Actions 入门:YAML 工作流基础与触发器配置
GitHub Actions 入门:YAML 工作流基础与触发器配置
GitHub Actions 入门:YAML 工作流基础与触发器配置

评论
使用 GitHub 账号登录后即可评论