适用场景
GitHub Actions 安全加固适用于以下场景:团队使用 GitHub Actions 构建、测试并部署生产代码;仓库包含数据库密码、云厂商密钥、API Token 等敏感凭据;CI 流程依赖第三方 action 或自定义脚本;使用自托管 runner 执行流水线。近几年供应链攻击频发,攻击者通过投毒流行 action、窃取 secrets、劫持 runner 等方式横向渗透,CI/CD 管道已成为攻破生产环境的捷径,本文给出可直接复制的加固配置。
前置条件
- 一个 GitHub 仓库(公开或私有均可),具有 Admin 权限
- 已启用 GitHub Actions(Settings → Actions → General)
- 如需自托管 runner,准备一台独立的 Linux 虚拟机或容器
原理说明
GitHub Actions 供应链攻击的核心是利用信任链:工作流中引用的第三方 action、运行环境变量、secrets 注入机制和 runner 权限。常见攻击路径有四条:一是action 投毒,攻击者向流行 action 提交恶意代码或劫持失活账户(如 2021 年 CVE-2021-32688 的 tj-actions/changed-files);二是secrets 泄露,恶意脚本通过环境变量或日志输出窃取仓库 secrets;三是 pull_request_target 滥用,让不可信 PR 代码运行在拥有 secrets 的上下文中;四是自托管 runner 失陷,runner 上残留的 secrets 和缓存被攻击者读取。加固思路是:最小化权限、隔离敏感上下文、固定依赖版本、加密存储凭据。
操作步骤
1. 工作流权限最小化
在仓库 Settings → Actions → General → Workflow permissions 中,将默认权限改为 Read repository contents,禁止 Actions 创建或审批 PR;对每个工作流通过 permissions 字段显式声明所需权限:
name: build
on: [push]
permissions:
contents: read # 只读仓库内容
packages: read # 只读容器包
issues: none # 不需要 issues 权限
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci && npm run build
2. Secrets 保护策略
将敏感信息存入 Settings → Secrets and variables → Actions,避免硬编码在仓库中。环境级 secrets 只注入到指定环境,防止开发分支访问生产凭据:
# 环境级 secrets 使用示例
jobs:
deploy:
runs-on: ubuntu-latest
environment: production # 仅 production 环境有 DEPLOY_KEY
steps:
- name: Deploy
env:
DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}
run: ./deploy.sh
关键参数:environment 将 secrets 作用域限定到指定环境,配合环境保护规则(必须审核才可部署)可大幅降低泄露面。
3. 第三方 action 固定版本与校验
引用第三方 action 时禁止使用 @main、@master 等浮动分支,必须固定到完整 commit SHA,并开启 Dependabot 自动更新:
# 不安全的写法
- uses: actions/checkout@main
# 安全写法:固定 commit SHA
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
同时限制仓库允许的 action 来源:Settings → Actions → General → Actions permissions 选择 Allow select actions,仅允许 GitHub 官方 action 和白名单仓库。
4. 防止 pull_request_target 滥用
pull_request_target 在默认分支上下文中运行,可访问 secrets。若必须使用,将不可信的 PR 代码放入单独步骤并用最小权限执行,且不要 checkout 并执行 PR 中的构建脚本:
# 危险用法:checkout PR 代码并运行
on: pull_request_target
jobs:
comment:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
# 绝不在此处执行 PR 代码中的脚本!
# 安全用法:只在默认分支上下文运行固定逻辑
on: pull_request_target
jobs:
verify:
runs-on: ubuntu-latest
steps:
- name: Comment PR
uses: actions/github-script@v7
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: '审核通过'
})
5. 自托管 runner 隔离
自托管 runner 与公司内网直接相连,是横向移动的跳板。为每个项目单独部署 runner,以专用低权限账号运行,禁止复用 root 账号;runner 所在主机不存放其他服务的凭据,并限制其网络出口(仅放行构建所需域名)。定期清理 runner 上的 _work 目录与缓存:
# 创建专用 runner 用户
sudo useradd -m -s /bin/bash runner-user
# 清理历史构建缓存
rm -rf /home/runner-user/actions-runner/_work/*
rm -rf /home/runner-user/actions-runner/_diag/*
# 限制网络出口(仅放行必要域名)
sudo iptables -A OUTPUT -d github.com -j ACCEPT
sudo iptables -A OUTPUT -d api.github.com -j ACCEPT
sudo iptables -A OUTPUT -j DROP
6. 审计与监控
开启仓库的 Audit log(Enterprise 版),定期检查 Secrets scanning 告警,将 Actions 运行日志接入日志平台。对 workflow 文件变更设置分支保护规则,要求 PR 审核后方可合并,防止攻击者直接修改 .github/workflows/ 注入恶意代码。
配置验证
# 1. 推送一个测试分支触发工作流,确认 permissions 生效
git push origin test
# 2. 在 Actions 页面检查每次运行的权限
# 打开运行详情 → Job summary,查看 "GITHUB_TOKEN Permissions" 仅含已声明项
# 3. 验证 secrets 不泄漏:在日志中 grep 关键字符串
# 若在 Actions 日志中搜索到 secrets 值,说明配置有误,立即撤销并轮换
常见问题
Q1:固定到 commit SHA 后,第三方 action 如何升级?
启用 Dependabot(Settings → Security → Dependabot alerts),它会自动为引用了旧 SHA 的 action 创建升级 PR。升级 PR 中应人工检查 action 的变更记录(尤其是 diff),确认无恶意改动后再合并,而不是盲目点击 “Update branch”。
Q2:secrets 是否安全?为什么说 secrets 放在 GitHub 不等于绝对安全?
secrets 加密存储在 GitHub 侧,但一旦工作流中被 ${{ secrets.XXX }} 引用,就会以明文注入到 runner 的进程环境变量,任何在该步骤内执行的恶意代码都能读取。因此除了存放位置,更要限制谁能在哪些上下文(环境、分支、PR)触发对 secrets 的访问,并对 secrets 设置有效期与轮换机制。
Q3:公开仓库使用 GitHub Actions 有什么额外风险?
公开仓库任何 fork 的 PR 都可能触发工作流(若配置了 pull_request 事件),攻击者可在 PR 中提交恶意 workflow 改动或利用已存在工作流的漏洞。建议公开仓库关闭 pull_request 自动触发、启用 “Require approval for first-time contributors”,并严格控制第三方 action 白名单。
总结
GitHub Actions 安全加固的核心是信任最小化:权限按需声明、secrets 环境隔离、action 固定 SHA、runner 独立隔离、工作流变更走审核。这五步配置成本低、见效快,能有效阻断绝大多数针对 CI/CD 管道的供应链攻击。建议将上述检查项写入团队安全基线,每季度复核一次。