GitHub Actions 需要 OIDC 受众约束
摘要
这篇博客文章认为,与 GitLab CI/CD 不同,GitHub Actions 缺乏静态的 OIDC 受众约束,而随着基于 OIDC 的联合身份验证越来越普遍,这一设计缺陷带来了日益增长的安全风险。
<p><a href="https://lobste.rs/s/ipt1em/github_actions_needs_oidc_audience">评论</a></p>
查看缓存全文
缓存时间: 2026/08/10 14:59
# GitHub Actions 需要 OIDC 受众约束
来源:https://blog.yossarian.net/2026/08/10/github-actions-needs-oidc-audience-constraints
## ENOSUCHBLOG
## *编程、哲学、骑行。*
- 首页 (https://blog.yossarian.net/)
- 标签 (https://blog.yossarian.net/tags)
- 系列 (https://blog.yossarian.net/series)
- 收藏 (https://blog.yossarian.net/favorites)
- 归档 (https://blog.yossarian.net/archive)
- 主站 (https://yossarian.net/)
- TILs (https://yossarian.net/til)
---
## *2026 年 8 月 10 日*标签:dear-github (https://blog.yossarian.net/tags#dear-github),oss (https://blog.yossarian.net/tags#oss),security (https://blog.yossarian.net/tags#security)
---
**TL;DR**:GitHub Actions 应该允许最终用户表达*受众约束*,使攻击者更难以在使用了独立 OIDC 承载作业的服务之间进行横向移动。他们可以通过相对较小的语法调整来实现这一点,尽管后端的影响可能并不小。
与许多 CI/CD 提供商一样,GitHub Actions 通过 OpenID Connect (https://openid.net/developers/how-connect-works/)(OIDC)提供可验证的机器身份[1](https://blog.yossarian.net/2026/08/10/github-actions-needs-oidc-audience-constraints#fn:ident)。
这些身份之所以很出色,原因有很多,尤其是它们允许在 GitHub Actions 上运行的工作流与(第三方)服务进行联合,而*无需* GitHub 对每次交互都进行中介和预先批准。
这就是 Trusted Publishing (https://docs.pypi.org/trusted-publishers/) 和 Sigstore (https://sigstore.dev/) 两者运作的支柱:GitHub Actions 上的一个工作流将其机器身份(通过 OIDC 令牌)提供给外部服务,外部服务对其进行身份验证,并出于某种目的(分别是上传到 PyPI 或签署工件)进行使用。
不幸的是,GitHub 在工作流中暴露 OIDC 令牌的*机制*存在一个重大弱点,而且(在我看来)这个弱点会随着时间推移带来日益严重的安全风险。这篇文章就是关于这个弱点的。
## CI/CD 和 OIDC [#](https://blog.yossarian.net/2026/08/10/github-actions-needs-oidc-audience-constraints#cicd-and-oidc)
所有“CI/CD 中的 OIDC”实现的核心都是某种机制,允许工作流(流水线定义等)请求或预先加载 OIDC 身份。
GitLab CI/CD 中的做法如下:
```yaml
my-job:
id_tokens:
PYPI_ID_TOKEN:
aud: pypi
script: |
do-something.sh --id-token "${PYPI_ID_TOKEN}"
```
GitHub Actions 中对应的是:
```yaml
my-job:
runs-on: ubuntu-latest
permissions:
id-token: write
steps:
run: |
resp=$(curl -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
"$ACTIONS_ID_TOKEN_REQUEST_URL&audience=pypi")
oidc_token=$(jq '.value' <<< "${resp}")
do-something.sh --token "${oidc_token}"
```
两者之间的差异很小但很重要:GitLab 要求*OIDC 受众*(`aud`)**事先静态声明**,而 GitHub 则要求工作流**动态请求**一个受众在*运行时*才选择的令牌(即 HTTP 请求中的 `audience` 参数)。
## 为什么这很重要?[#](https://blog.yossarian.net/2026/08/10/github-actions-needs-oidc-audience-constraints#why-does-this-matter)
首先,快速了解一下 OIDC。
在底层,OIDC(大部分)只是 OAuth 2.0 (https://oauth.net/2/),而 OIDC 身份令牌只是 JSON Web Token (https://datatracker.ietf.org/doc/html/rfc7519)(JWT),外加一些对它们应包含的声明(claims)的更严格[2](https://blog.yossarian.net/2026/08/10/github-actions-needs-oidc-audience-constraints#fn:stringent)约束。
OIDC ID 令牌中最重要的声明可以说是 `sub`,因为它标识了主体(“subject”)[3](https://blog.yossarian.net/2026/08/10/github-actions-needs-oidc-audience-constraints#fn:sub)。
然而,*其次*最重要的声明是 `aud`,即*受众*。受众*至关重要*,因为它**约束了谁会接受该令牌**:接受 ID 令牌的服务只有在确认受众与其自身匹配时才应该接受。
换句话说:`aud` 声明防止了一个*特意*为某个服务签发的 ID 令牌被攻击者窃取并*误用*到另一个服务上。
这旨在作为一种纵深防御:即使攻击者设法窃取了 OIDC 凭据,他们也不应该能够利用它*横向移动*到其他服务。
这是一种脆弱的防御,但通常*颇为有效*[4](https://blog.yossarian.net/2026/08/10/github-actions-needs-oidc-audience-constraints#fn:effective),**除非**你让攻击者也能控制 `aud` 声明。
不幸的是,这正是 GitHub Actions 所允许的[5](https://blog.yossarian.net/2026/08/10/github-actions-needs-oidc-audience-constraints#fn:others):`id-token: write` 赋予作业(或整个工作流)生成它想要的*任何* ID 令牌的能力,受众也*任意*。
在一个(我们的世界中)被授予 `id-token: write` 的作业*同时*也会运行大量第三方代码的世界里,这一点关系重大:这些代码中的任何漏洞(或恶意软件)都可能请求它*本不*应该有权访问的受众的*新* ID 令牌。
我们在设计 Trusted Publishing 时思考过这个问题,并得出结论:Trusted Publishing 使用的机器身份*必须*包含工作流名称,以防止攻击者通过从 `aws-deploy.yml` 中诱导一个 `aud: pypi` 的 ID 令牌来冒充 `pypi-publish.yml`。
然而,这种约束并不适用于所有可能的用例:许多集成只想将 `org/repo` 标识符作为足够的身份,这意味着在签发 ID 令牌时,所有工作流实际上是同级的。
## GitHub 应该怎么做?[#](https://blog.yossarian.net/2026/08/10/github-actions-needs-oidc-audience-constraints#what-should-github-do)
增加约束!理想情况下是这样的:
```yaml
my-job:
runs-on: ubuntu-latest
permissions:
id-token: [pypi]
```
这里的基本思想是*约束*作业可以为其请求 ID 令牌的受众。在上面的例子中,尝试请求 `aud: sts.amazonaws.com` 的令牌会导致错误,从而防止一个*本意*仅为发布到 PyPI 的作业被用作通往 AWS 的跳板。
当然,这可能会有一些缺点。例如,某些服务可能(不明智地)在其受众中要求动态信息,这意味着无法静态预声明一个值。我认为在实践中这种情况足够罕见,不值得因此阻止一项安全改进;而且当这种情况*确实*发生时,GitHub 可以继续允许 `id-token: write` 形式作为一种(安全性较低!的)兜底方案。
---
相似文章
GitHub 已不适配新时代的形态
这篇博客文章认为,GitHub 的协作模式(分支、拉取请求、代码审查)已难以适应现代 AI 驱动的软件开发时代——在这个时代,LLM 和智能体以极高速度生成代码,因此需要重新思考工具和工作流程。
GitHub 对开发者年龄验证的看法
GitHub 的博文解释了旨在保护未成年人网络安全的年龄验证法律,可能会无意中给开源开发者和基础设施带来负担,并敦促政策制定者合理界定此类法律的范围。
GitHub Actions 中 GitHub_TOKEN 在日志中泄露的问题
GitHub Actions 中的一个安全漏洞导致 GitHub_TOKEN 在日志中泄露,可能暴露凭据。
GitHub 不应成为在 crates.io 上发布 Rust 的依赖条件
一篇讨论在 crates.io 上发布 Rust 包时对 GitHub 的依赖的文章,主张这种依赖不应是强制性的。
@dabit3:一个非常有趣的故事,展示了 @github 目前的状态无法有效保护开源维护者免受 AI 滥用的影响。
一个故事描述了在发布 900 美元赏金后,AI 机器人如何用垃圾评论和未经测试的 PR 淹没了一个 GitHub 仓库,迫使维护者实施如贡献者白名单和声誉机器人等变通方法,凸显了 GitHub 缺乏反机器人机制。