切换语言
切换主题

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 仓库设置里创建 stagingproduction 两个环境,分别配置不同的 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 端(控制台操作):

  1. 创建 IAM Identity Provider,URL 设为 https://token.actions.githubusercontent.com
  2. 创建 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官方文档关键词
AWSaws-actions/configure-aws-credentialsOIDC federation
GCPgoogle-github-actions/authWorkload Identity Federation
Azureazure/loginFederated 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。

教训

这次事件暴露了几个问题:

  1. Action 版本号标签不可信——攻击者可以修改标签指向恶意 commit
  2. 第三方 action 能访问你的 secrets——一旦被入侵,所有 secrets 都暴露
  3. 历史运行日志可能泄露敏感信息——即使现在修复了,过去的运行记录里可能仍有痕迹

你的检查清单

如果你曾经使用过 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

    步骤1: 创建 IAM Identity Provider

    在 AWS IAM 控制台创建身份提供商:

    • Provider URL: https://token.actions.githubusercontent.com
    • Audience: sts.amazonaws.com
    • 记录生成的 Provider ARN
  2. 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

    步骤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

    步骤4: 测试和验证

    运行 workflow 验证 OIDC 配置:

    • 检查 workflow 日志,确认认证成功
    • 验证 role-to-assume 正确
    • 确认无需任何静态凭证即可访问 AWS 资源
    • 测试目标操作(如 S3 同步、ECR 推送等)

常见问题

Repository Secrets 和 Environment Secrets 有什么区别?
Repository Secrets 存储在仓库级别,所有 workflow 都能访问。Environment Secrets 按环境隔离,只有引用该环境的 job 才能访问对应的 secrets,还支持审批流程。多环境部署时推荐使用 Environment Secrets。
如何在 workflow 中安全地使用 secrets?
遵循三个原则:

• 显式传递:通过 env 字段注入,不要内联使用 ${{ secrets.XXX }}
• 最小权限:只传递给需要的 step,使用 GITHUB_TOKEN 时设置 permissions: contents: read
• 定期轮换:建议 30-90 天轮换一次,云部署可使用 OIDC 完全跳过轮换
什么是 OIDC?为什么比传统 secrets 更安全?
OIDC(OpenID Connect)让 GitHub 作为身份提供商向云平台证明 workflow 身份,云平台发放短期 JWT 令牌。优势:无需存储静态凭证、无需轮换、不存在泄露风险。认证延迟比传统 secrets 方式降低约 87%。
如何防止供应链攻击(如 tj-actions 事件)?
核心防护措施:

• Pin Actions 到 commit SHA,不要用版本号标签
• 限制 secrets 仅传递给信任的 actions
• 审计第三方 action 的 maintainer 来源
• 定期运行 Dependabot 或 Renovate 检查版本更新
secrets 会在日志中泄露吗?
GitHub 会自动将 ${{ secrets.XXX }} 在日志中替换为 ***。但如果直接使用环境变量(如 echo ${MY_TOKEN}),日志会显示真实值。建议测试 workflow 日志,确认没有意外暴露。
Organization Secrets 适合什么场景?
适合团队多仓库、共享凭证的场景。在组织级别配置一次,所有仓库都能使用。可控制访问范围:所有仓库或指定的仓库列表。例如多个项目共享 AWS_ACCESS_KEY 时,Organization Secrets 可避免在每个仓库重复配置。

11 分钟阅读 · 发布于: 2026年4月18日 · 修改于: 2026年4月20日

当前属于系列阅读 第 5 / 5 篇

GitHub Actions 完全指南

如果你是从搜索进入这篇文章,建议顺手补上上一篇或继续下一篇,这样更容易把同一主题读完整。

查看系列总览

相关文章

BetterLink

想持续收到这个主题的更新?

你可以直接关注作者更新、订阅 RSS,或者继续沿着系列入口往下读,避免下次又回到搜索结果重新找。

关注公众号

评论

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