版本标签是可以移动的
工作流里常见的写法是 uses: actions/checkout@v4。问题是 v4 只是一个可以随时被重新指向的标签——仓库作者(或拿到仓库权限的人)推一个新提交、把标签挪过去,你下次运行拉到的就是另一份代码。
这不是理论风险。第三方 action 拿到 GITHUB_TOKEN 之后,在权限放开的情况下能改 release、能推代码。一个被接管的 action,等于你主动把 CI 的钥匙递过去。
固定到完整 commit SHA 是唯一能锁住内容的方式:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
光锁 SHA 不够,还要限权
仓库设置决定了 GITHUB_TOKEN 的默认权限。新仓库默认只读,但老仓库、以及为了跑通流水线而手动放开过权限的项目,往往还停在可写状态。别赌默认值,在工作流顶层显式收紧:
permissions:
contents: read
需要写的时候,只在具体 job 上开对应权限。这样即使某个 action 被投毒,它能造成的破坏也被限制在「读代码」这个级别。
最危险的两个触发器
pull_request_target 会在目标仓库的上下文里运行,并且能拿到 secrets。如果它又 checkout 了 PR 里的代码,等于让外部贡献者在你的凭据环境里执行任意脚本。这个组合出过不止一次真实事故。
另一个是 workflow_run,它能在有 secrets 的环境里运行,但触发源可能是 fork 的 PR。用之前先想清楚:这段代码是谁写的。
密钥:能不用长期凭据就不用
云厂商的 access key 放在 secrets 里,一旦工作流被攻破就直接泄露。更好的做法是 OIDC 联合身份:工作流向云厂商换取短期凭据,不落盘、不过期可用。
AWS、GCP、Azure 都支持,配置比想象中简单,代价是要多写几行信任策略。
让依赖自己更新
SHA 锁死之后,升级成了手工活。用 Dependabot 的 github-actions 生态可以自动提 PR,把 SHA 和注释一起更新:
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
这样你既能锁内容,又不会锁死版本。
一个检查清单
- 所有
uses:都是 40 位 SHA,后面跟版本注释; - 顶层
permissions默认只读; - 没有
pull_request_target+ checkout PR 代码的组合; - 云凭据优先 OIDC,长期 key 有轮换计划;
- 第三方 action 数量控制在个位数,每个都看过源码或至少看过发布记录。
我的判断
CI 是现代软件里权限最集中的地方之一:它能读代码、能发布、能部署、能拿密钥。偏偏它的配置往往是最随意的那部分——复制粘贴一段 YAML,跑通就不再看。
固定 SHA 和最小权限,成本是每次升级多花两分钟,收益是把「一个第三方仓库被接管」从「全线失守」降级成「一次失败的构建」。










暂无评论内容