配了三年 CI,每次堵 runner 打电话回家都要靠第三方工具——今天 GitHub 把这件事从平台层原生化了

配了三年 CI,每次 runner 能打电话回家这件事都是靠第三方工具才堵上的——今天 GitHub 把这件事从平台层彻底原生化了

过去几年,CI/CD 供应链攻击频发,根本原因之一是 runner 网络访问完全无管控。tj-actions/changed-files 被污染后,23,000 个仓库的凭证在几小时内全部泄漏——攻击者没有破解任何代码,只靠 runner 能自由向外发请求这一个条件,就完成了数据外传。

GitHub 在 2026 年安全路线图中拿出了三层防御,其中最关键的就是原生出口防火墙——不是靠 workflow 里加 action,是在 runner 外层直接建墙。


为什么 runner 能打电话回家是个问题

GitHub-hosted runner 的网络默认是完全开放的——任何步骤里的任何命令,只要能联网,就可以向外发请求。攻击者拿到写权限(比如通过 supply chain attack 污染了一个 action)之后,可以:

  1. 在 step 里植入恶意脚本
  2. 脚本把 runner 内存中的 GITHUB_TOKEN、AWS keys、npm tokens 编码成 DNS 查询或 HTTP 请求
  3. 发送到攻击者控制的服务器

凭证就这样流出了。全程没有任何越权操作,每一步都是”正常”操作——因为 runner 本来就不限制出站流量。


GitHub 原生出口防火墙是怎么设计的

这个防火墙运行在 Layer 7,位置在 runner VM 外部。即使攻击者拿到了 runner 的 root 权限、篡改了内部配置,防火墙本身也无法被绕过——因为它不在 runner 里。

核心机制:

Monitor 模式:记录所有出站请求,自动关联到发起请求的 workflow run、job、step 和具体命令。你不需要做任何配置,就能看到”我的 CI 现在都在访问哪些域名”。

Enforce 模式:只允许白名单内的出站目的地,名单外的所有请求被直接拦截。结合 Monitor 模式,企业可以先用 Monitor 积累真实流量数据,再切换到 Enforce——不是一刀切,是数据驱动的。

配置维度包括:

  • 允许的域名 / IP 范围(如 github.comregistry.npmjs.org
  • 允许的 HTTP 方法(GET/POST/PUT/DELETE)
  • TLS 版本要求
  • 每个连接都标注来源 job/step,可审计

每条被拦截的请求都能回溯到是哪个 workflow、哪个 job、哪个 step 发起的,不是只知道”有一个请求被拒了”,而是精确到执行上下文。


和现有方案的差距在哪里

实际上,已经有第三方工具在解决这个问题的。StepSecurity 的 Harden-Runner(Apache-2.0 开源,4000+ 开源项目在使用)提供了完整的出口流量过滤和运行时安全能力,CISA/Microsoft/Google 等都在用。

但它有几个天然局限:

它在 runner 内部。Harden-Runner 以 GitHub Action 的形式运行在 workflow 里,攻击者如果拿到了足够高的权限,理论上可以修改它的配置或行为。

它需要每个 workflow 手动接入。需要把 step-security/harden-runner 加到每个 job 的第一步,没有全局强制能力。

GitHub 原生防火墙的差异点就在这里:平台级能力,不需要每个 workflow 配置,跑在 runner 外面无法被内部篡改。

Timeline:公开预览预计 2026 年 Q3-Q4。


依赖锁了还不够,攻击面还有另一层

回顾 tj-actions 事件,GitHub 同步在推的另一个核心能力是 Workflow Dependency Locking——类似 go.mod + go.sum,把 workflow YAML 里的 dependencies: 段落展开成完整依赖树,用 commit SHA + SHA-256 hash 锁定所有直接依赖和传递依赖。

但这个能力的局限是:它解决了”谁在跑”的问题,没解决”跑的时候能访问什么网络”的问题。Dependency Locking 保证了你引用的是原始 commit(没被篡改),但如果这个 commit 本身包含了恶意代码,或者攻击者通过漏洞从外部加载了恶意依赖,依赖锁本身拦不住数据外传。

出口防火墙是纵深防御的第三层:

层次 能力 解决的问题
第一层 Dependency Locking 引用的是原始代码,不是被篡改过的版本
第二层 Scoped Secrets 泄露一个 step 的凭证,影响范围不扩散到全 workflow
第三层 Egress Firewall 即使代码被污染,runner 也无法把数据传出去

三层配合,才构成完整的 CI/CD 安全体系。


现在能做什么

出口防火墙公开预览还需要几个月,但有几件事现在就能做:

第一步:把 top-level action 全部 pin 到 commit SHA

不要再用 uses: actions/checkout@v4,改成 uses: actions/checkout@a829e3d...(40 位 commit SHA)。SHA pinning 解决的是直接依赖被标签重定向的问题,不完美,但堵住了最常见的攻击路径。

社区工具 gh-actions-lockfile(Garen Torikian 开发)可以自动生成 lockfile,等 GitHub 原生支持出来后可以直接迁移。

第二步:接入 Harden-Runner 审计模式

在所有 workflow 的第一个 job 加一行:

steps:
  - uses: step-security/harden-runner@eb238b55efaa70779f274895e782ed17c84f2895
    with:
      egress-policy: audit

这不会拦截任何流量,但会生成可视化的出站请求地图。每个 workflow 真实访问了哪些域名一目了然——这些数据就是以后写 Enforce 白名单的原材料。

第三步:用 OIDC Custom Property Claims 做访问控制

这个能力在 2026 年 4 月已经 GA,不需要等。用组织级别的自定义属性(team、env、tier)替代仓库名称列表写进云厂商 IAM trust policy——仓库增减时 OIDC token 自动更新,不用改 yaml。


三层防御不是可选项。CI/CD 已经是现代软件供应链里权限最集中的节点——有源码读写权限、有云凭证、有部署通道。攻击者不需要找零日漏洞,只要污染一个依赖就行。GitHub 正在把 CI/CD 安全从”靠团队自觉”变成”平台默认”。这个转变不会平滑,但方向是对的。

立即可落地清单:

  • [ ] 把 workflow 里的 @v1@latest 标签全换成 commit SHA
  • [ ] 所有 job 第一个 step 接入 step-security/harden-runner,先跑 audit
  • [ ] 跑完一轮 CI 后,整理出访问域名白名单,准备迁移到 Enforce 模式
  • [ ] 用 GitHub 仓库自定义属性(team/env/tier)配置 OIDC trust policy,删除硬编码仓库名列表

评论区

0 条评论

登录后可评论。