首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >我扒了GitHub Actions锁文件,发现它只锁住了半条命

我扒了GitHub Actions锁文件,发现它只锁住了半条命

原创
作者头像
Lethehong
发布于 2026-11-14 21:21:31
发布于 2026-11-14 21:21:31
220
举报
文章被收录于专栏:速递日报速递日报

图 1 本文配图:围绕本文主题的氛围配图

锁文件不是给全流程上锁

2026年11月14日,InfoQ中文把这条消息放了出来,GitHub Actions的锁文件功能正式可用。官方的措辞很省,只说保障工作流依赖安全。我第一反应是,终于有人管管uses后面那个@v3了。

但你如果以为装上它就万事大吉,那大概率会踩坑。我花了一个下午把它的行为边界摸了一遍,结论是,它锁住的只是action引用这一层,工作流里真正跑命令的那部分,它完全没碰。

这不是说它没用。恰恰相反,在它覆盖的范围内,它做的事情很实在。问题在于很多人会把「依赖安全」理解成「流水线安全」,这两件事差着好几层。

所以这篇我不打算复述官方公告。我想拆的是,它到底在哪一个判断上生效,哪一段数据流它管不到,以及你今天该拿它怎么办。

它锁的是引用,不是内容

先把机制说清楚。你在workflow文件里写一行uses: actions/checkout@v4,这里的v4是一个可变的tag。维护者随时可以把这个tag指向新的commit,你的下一次运行就会拉到不同的代码。

锁文件干的事情,是把这次解析到的commit SHA记下来。下次运行的时候,它先比对锁文件里记的SHA和远端tag当前指向的SHA,不一致就报错或者按你配置的策略处理。

换句话说,它防的是tag漂移。上游把v4从commit A挪到commit B,你的流水线不会悄无声息地跟着跑B,而是会停下来告诉你,锁文件里记的是A。

这个机制在包管理领域很成熟。npm有package-lock.json,Go有go.sum,Cargo有Cargo.lock。GitHub这次是把同一套思路搬到了action引用上。

图 2 本文示意图:工作流从解析到执行要过四层,锁文件只在引用解析这一层生效,下面三层全靠你自己。

缺陷长在解析层和锁文件层之间

我把它的校验路径画了一遍。工作流启动的时候,runner先解析workflow文件里的uses字段,拿到owner/repo@ref这个三元组。然后它去查锁文件,看这个三元组有没有对应的SHA记录。

图 3 本文示意图:从读取 workflow 到决定是否放行,锁文件只在第三个判断上介入,前后两步它都不参与。

关键判断在这里,如果锁文件里没有这条记录,它是直接放行还是拒绝。公开信息里我没有找到默认行为的明确说明,从功能定位推断,首次遇到未记录的引用时应该是以记录为主,而不是阻断。

这就意味着,锁文件对「第一次见到」的引用是不设防的。如果你的仓库是刚启用这个功能,或者有人往workflow里新加了一个uses,那一次运行拉到的就是当时tag指向的任意commit。

这不是漏洞,这是设计上的取舍。锁文件解决的是「已知引用被篡改」,不解决「未知引用是否可信」。这两件事在威胁模型上完全不同。

run步骤和容器镜像完全在锁文件视野之外

这是我最想让你记住的一点。锁文件只管uses那一行,workflow里还有大量的run步骤,里面可以写任意shell命令。

一个典型的run步骤长这样,curl -fsSL 某个地址 | bash。这条命令拉下来的脚本内容,锁文件不记录、不校验、不比对。上游今天返回的脚本和明天返回的脚本可以完全不同,你的流水线照跑不误。

图 4 本文示意图:左边是我最初的预期,右边是拆完之后看到的实际边界,差的那几条才是你真正要自己补的。

容器镜像同理。jobs里用container字段指定的镜像,或者run步骤里docker pull拉下来的镜像,锁文件都不管。镜像tag同样是可变的,latest今天指向哪个digest,明天可能就变了。

所以如果你问我,锁文件能不能替代对run步骤的审计,答案是不能。它把action引用这一层收紧了,但流水线里真正执行任意代码的那些位置,还是靠你自己盯。

三个前提,缺一个它就不生效

第一个前提,你的仓库得启用这个功能。公开信息没有披露它是默认开启还是需要手动配置,从以往GitHub功能的习惯推断,大概率是需要在仓库设置或者workflow层面显式声明的。

第二个前提,锁文件得被提交到仓库里。如果它只存在于runner的临时目录,那每次运行都是全新解析,等于没锁。这一点和package-lock.json的逻辑一样,文件不在版本控制里就没有意义。

第三个前提,你的团队得接受它带来的摩擦。上游发新版本,你的流水线会因为SHA不匹配而失败,这时候需要有人去更新锁文件。这个更新动作如果不进流程,锁文件很快就会变成没人维护的摆设。

三个前提里,我认为第三个最容易出问题。技术机制再严密,只要更新锁文件这件事没有明确的责任人,它就会在第一次告警之后被绕过。

今天就能做的三件事

第一件,立刻做的。把你所有workflow文件里的uses行过一遍,凡是写@v3、@main、@master这种可变引用的,全部改成完整的commit SHA。这一步不需要等锁文件功能,今天就能做,效果是立竿见影的。

第二件,这周做的。在你的仓库里启用锁文件功能,把生成的锁文件提交进去,然后在CI里加一条检查,锁文件有变更但没有人review的PR不允许合并。

扫出所有可变引用(示意代码,列出workflow里所有没有固定到commit SHA的uses行)

第三件,做不到上面两条时的过渡办法。至少给你的run步骤里所有curl | bash加上校验,下载完先算sha256再执行。容器镜像不要用latest,用digest引用。

这三件事的优先级是递减的。第一条是根治,第二条是制度化,第三条是缓解。如果你只能做一件,做第一件。

给curl加校验再执行(示意代码,把curl管道执行改成先下载再校验sha256最后执行)

我没查到的那部分

锁文件的默认启用状态,公开信息没有披露。我倾向于认为需要手动开启,但这只是推断,没有验证。

锁文件在SHA不匹配时的默认行为,是直接失败还是告警后继续,我也没有找到明确说明。这个行为直接决定了它是强约束还是弱提醒,差别很大。

还有一个我没能确认的点,锁文件是否覆盖reusable workflow的调用链。如果你的workflow调用了另一个仓库的reusable workflow,那个workflow内部的uses是否也被锁文件管理,公开材料里没有写清楚。

这些空白不影响你现在动手,但如果你要做严格的供应链安全评估,这几条得等官方文档补全或者自己实测确认。

我怎么验的

我没有搭完整的GitHub Actions环境跑真实流水线,这一点得先说清楚。我的验证方式分两步,第一步是读官方文档和功能描述,把锁文件声称覆盖的范围圈出来。

第二步是拿一个测试仓库,手动构造了几种workflow写法,包括固定tag、浮动tag、带run步骤的、带container字段的,然后逐条对照锁文件会不会介入。这个过程中我没有截图,也没有跑完整的CI,所以严格说这不算实测,算文档推演加局部行为观察。

我能确认的是,锁文件的校验发生在uses解析阶段,run步骤和container字段不在它的检查路径上。这个结论来自功能定位和文档描述的一致性,不是我抓包抓出来的。

所以如果你要拿这篇去做安全评审的依据,我建议你自己再跑一遍,特别是锁文件缺失记录时的默认行为,那个点我确实没验到。

写在最后

锁文件这件事让我想到一个更普遍的问题。我们总希望一个开关就能把安全搞定,但供应链安全的本质是,每一个引入外部代码的位置都是一个决策点。锁文件帮你管住了其中一个,剩下的位置你得自己知道它们在哪。

以上,既然看到这里了,如果觉得不错,随手点个赞、在看、转发三连吧,如果想第一时间收到推送,也可以给我个星标⭐~

谢谢你看我的文章,我们,下次再见。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 锁文件不是给全流程上锁
  • 它锁的是引用,不是内容
  • 缺陷长在解析层和锁文件层之间
  • run步骤和容器镜像完全在锁文件视野之外
  • 三个前提,缺一个它就不生效
  • 今天就能做的三件事
  • 我没查到的那部分
  • 我怎么验的
  • 写在最后
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档