在代码审查中,一个名字类似 security-audit.yml 的工作流文件往往会被当作可信的基础设施:它的职责看起来是发现问题,而不是引入风险。恰恰是这种身份预设,使得攻击面被折叠进“例行审计”这四个字里——审查者关心它报告了什么,却容易忽略它实际能做什么。近期 GitHub Actions 上出现伪装成安全审计的工作流影响多个仓库的线索,把同一个问题重新摆到台前:一条工作流只要被触发,它在仓库里获得的代码只读与凭据访问能力,往往远超“审计”这个名分所暗示的范围。本文从这条信任盲区切入,先拆解工作流拿到代码与凭据的路径,再给出可复核的核查、移除与轮换流程。

“安全审计”工作流为何容易获得信任
安全类工作流在审查中天然占便宜。它的存在理由正当,运行结果通常以报告、注释或检查结论的形式呈现,审查者下意识把它归类为只读的观察者。而 GitHub 的运行时模型并不区分“审计者”与“操作者”:GitHub 官方文档明确指出,工作流中任何一个第三方 action 被攻陷都可能非常显著,因为该 action 会获得仓库上配置的全部 secrets,并可能使用 GITHUB_TOKEN 向仓库写入。换言之,名分上的只读职责,与运行时的实际权限之间没有强制对应关系。
另一个被低估的假设是“仓库内的工作流 = 我们自己写的工作流”。实际流水线里,审计任务通常委托给第三方 action,或在 job 之间共享工作目录与上下文。GitHub 文档提示,工作流中的各个 job 可以相互影响:一个 job 可以查询后续 job 使用的环境变量、向共享目录写入后续 job 会处理的文件,甚至直接相互干预。这意味着即使唤起审计的入口是可信的,审计 task 本身仍可能成为攻击链的一环。
拆解:工作流获得代码与凭据访问的路径
触发条件决定了谁能让工作流运行
触发条件决定了工作流的暴露面。长期使用 pull_request_target、issues 或 schedule 的工作流,在拉取请求、议题事件或定时器到点时自动运行,外部输入与仓库凭据处于同一次执行中。审查时需要回答两个问题:这个触发方式是否必须存在?触发后运行的内容是否包含不可信的第三方代码或用户输入?如果“安全审计”只在发布前需要运行,那么它就不必在每次外部 PR 上被触发。
permissions 决定 GITHUB_TOKEN 的能力边界
GitHub 文档建议将 GITHUB_TOKEN 的默认权限设置为只读取仓库内容,再按工作流文件中的个别任务需要提升权限。审查应核对每个工作流的 permissions 块,尤其是显式声明了 contents: write、issues: write、pull-requests: write、actions: write 的工作流。写权限不应出现在只做读取与分析的任务上;若必须写入,应只授予需要写入的那个 job,而不是整份工作流。
secret 引用暴露了哪些凭据可被接触
仓库中任何具有写访问权限的用户都能读取仓库配置的所有 secret,因此工作流实际可触达的凭据范围,就是仓库 secret 的面板清单。审查时逐项对照:该工作流是否真的需要这些 secret?是否可以用 OIDC 换取短期云凭据,从而去掉长期密钥?敏感值是否被正确屏蔽?GitHub 文档特别要求敏感数据不得以明文存储在 workflow 文件中,并建议不要用 JSON、XML 或 YAML 这类结构化数据封装密码值,因为脱敏依赖对具体值的完全匹配,结构化封装会显著降低正确编校的概率;每个敏感值应单独设置为一个 secret。
第三方 Action 的引用方式决定代码是否可变
引用方式是供应链上最容易被放过的一环。GitHub 文档给出的结论很直接:将 action 固定到全长度提交 SHA 是目前把 action 当作不可变发布使用的唯一方式;固定到特定 SHA 有助于降低攻击者在 action 仓库中添加后门的风险。固定时应选择 action 官方仓库而不是 fork 中的 SHA,并检查 action 对仓库内容与 secrets 的处理是否与预期一致。使用 @v3、@main 这类浮动标签时,标签背后的代码可以在不改变工作流文件的情况下被替换,审查记录因此失去意义。
用最小权限与引用固定收紧暴露面
把上面四个维度收成一条基线,可以在不改变流水线功能的前提下显著降低单点被攻陷时的损失:
- 顶层
permissions先设为只读,再对具体 job 做最小提权; - 所有第三方 action 固定到全长度提交 SHA,优先来自 action 官方仓库;
- 云端访问改用 OIDC 换取短期凭据,避免长期云密钥进入仓库 secret;
- 把不可信构建 job 与特权发布 job 分开,两者之间只传递经过校验的产物,特权 job 不执行来自不可信产物中的脚本;
- 尽可能减少第三方 action 的数量,缩小供应链攻击面;
- 敏感值单独设为 secret,不在 workflow 文件中出现明文。
下面是一段最小示例,用来说明权限收敛的写法,不构成完整方案。contents: read 是基线,需要写操作的任务单独提升——注意 actions: write 只授权给确实需要读写工作流运行的 job:
permissions:
contents: read
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@<full-length-commit-sha>
- name: Run audit tool
uses: <org>/audit-action@<full-length-commit-sha>
publish-report:
needs: audit
permissions:
contents: read
actions: write
steps:
- uses: actions/download-artifact@<full-length-commit-sha>这段示例的边界需要说清楚:SHA 必须替换为来自 action 官方仓库的真实全长度提交 ID;actions: write 是否必要取决于报告的发布方式,如果报告只是作为 job 输出或由专门的管理员流程处理,这个写权限就不该出现在工作流里。误报的角度看,把浮动标签一键替换成一串哈希会让部分团队的 Dependabot 告警变得静默——GitHub 文档说明 Dependabot 仅为使用语义版本控制的 action 创建告警,不会为固定到 SHA 的 action 创建告警,因此需要配合依赖项审查或其它定期人工复核来保持固定值不过时。
授权仓库内的隔离核查步骤
当怀疑某条伪装成审计的工作流时,推荐把动作限定在自己有书面授权的仓库中,按准备、执行、判断三段走。
准备
- 记录核查范围:涉及哪些仓库、哪些工作流、哪些 secret 名称与权限;
- 导出待查工作流文件的完整版本历史,保留审查依据;
- 在不影响业务的时间窗内进行,确认该工作流是否与发布、同步、报告依赖相关;
- 确认事件响应负责人与回滚方式(例如临时禁用工作流而非直接删除)。
执行
- 检查触发条件:列出
on块中的所有事件,标出自动触发且接受外部输入或外部代码的路径; - 检查
permissions:对照每个 job 实际用到的能力,标出超出需要的写权限; - 检查 secret 引用:列出工作流中引用的所有 secret,确认每个都用在该工作流内且无法缩小范围;
- 检查第三方 action 引用:逐个比对是否固定到全长度提交 SHA、SHA 是否来自官方仓库、作者与用途是否匹配;
- 检查运行日志与产物来源:确认报告与产物的生成步骤,是否包含向外部地址发送数据、是否将仓库内容写入工作区之外的位置、是否为其它 job 留下可被利用的共享文件。
结果判断
判定为可疑的门槛不是“有写权限”或“用了第三方 action”这类单一项,而是组合:自动触发 + 宽写权限 + 浮动的第三方引用 + 可触达的高价值 secret。任一组合成立,都应先隔离工作流(临时设为 disabled 或移除 on 块中的高风险触发),再进入下一步处置。
移除、凭据轮换与回归验证
隔离之后按污染面处置,而不是只删文件:
- 移除或重写:删除已确认恶意的工作流文件,或在保留审计功能的前提下重写为最小权限、SHA 固定的版本;若改为依赖托管 action,先审查该 action 的源码与权限需求。
- 轮换凭据:如果工作流运行日志中出现未脱敏的 secret,GitHub 文档建议删除日志并轮换 secret。实践上对所有可能被该工作流接触过的凭据都做轮换,包括仓库 secret、组织级 secret 以及可能被写权限影响的发布凭据。轮换后复核旧的凭据值已失效。
- 回收痕迹:检查工作流运行记录、产物、comment 与议题中是否有异常写入,必要时撤销由令牌产生的异常提交或分支保护修改。
- 回归验证:让审计流程以最小权限重新运行一次,确认所需能力可用、超出需要的写权限已不可用、第三方 action 的固定 SHA 仍然有效。回归验证不只看“跑成功”,还要看“用不到的能力确实用不上”。
- 留档与复评:把本轮的核查记录、判定依据和变更一并归档,并安排下一次复核的时间点。

把检查变成可复核的长期控制
单次清理不足以对抗“审计名义”这类信任盲区,需要把上面的人工判断沉淀成流水线自身的控制:
- 仓库默认把
GITHUB_TOKEN权限设为只读,提权改动在 PR 审查中显式标注; - 对工作流文件变更启用依赖项审查,让第三方 action 的引用变化在合并前可见;
- 使用 CodeQL 的 Actions 扫描或其它工作流静态检查,将被识别出的可疑工作流模式纳入审查提示;
- 把 SHA 固定的更新交给仍保留告警能力的机制或定期人工复核,避免固定动作导致告警静默;
- 对高价值凭据设置轮换周期,并把工作流运行日志作为轮换触发条件之一。
这套控制的重点不在于阻断所有第三方 action,而在于让“一条工作流实际上能做什么”始终可被追问、可被复核、可被回退。平台的具体权限语义、默认设置与事件行为会随版本演进,落地前应以 GitHub 官方文档为准,并在自己的授权环境中做一次回归验证,再扩大到组织范围。
- it2021
- it2021.com
- test





山东 烟台 1F
我们仓库审计工作流的权限还真没细看,得回去查查
西藏 山南 2F
固定完整SHA这点太关键了,浮动标签真容易出事
西藏 山南 3F
安全审计反而最容易被放过,这个角度确实真实
广东 深圳 4F
用OIDC换短期凭据,现有流水线改动大吗
重庆 重庆 5F
一直以为审计工作流只读,原来权限这么大
6F
自动触发加宽写权限再加浮动引用,组合起来确实危险