pinact:把 GitHub Actions 钉死在 SHA 上,并让版本注释可验证
pinact 是一个 CLI,用于编辑 GitHub 工作流和复合操作文件以及操作和可重用工作流的 pin 版本。 pinact还可以更新其版本并验证版本注释。
秒懂
- 它是什么?
- pinact 是一个用 Go 写的 CLI,用于把 GitHub Actions 和 Reusable Workflows 的版本固定到完整 SHA,还能更新版本、校验版本注释,并支持离线检查。它适合想收紧供应链安全但又不想手改一堆 workflow 文件的团队。
- 适合谁用?
- 如果你的仓库里有很多 workflow 文件,并且你希望把 actions 固定到完整 SHA 而不是 tag 或分支,pinact 值得一试。它特别适合那些已经用 reviewdog 做代码审查、或者想接入 GitHub Code Scanning 的团队,因为 SARIF 输出可以直接对接。
- 能商用吗?
- 可以。MIT 是宽松许可证:你可以使用、修改并销售基于它的软件,只需保留版权和许可证声明。
- 还在维护吗?
- 在维护。仓库最近一次提交在 2 天前。
- 用什么语言写的?
- 主要是 Go(依据 GitHub 的语言统计)。
以上回答依据项目的 GitHub 数据(最近同步于 2026年9月14日)和我们的分析,不构成法律意见。
开源项目深度解析
它解决的是 workflow 文件里的版本漂移问题
GitHub Actions 的常见写法是 actions/checkout@v3 或 @main,这种写法方便,但供应链上不严谨。tag 可以被覆盖,分支会移动,你昨天验证过的代码今天可能就不是同一份。pinact 的定位就是把这个事情反过来:它把 actions 和 reusable workflows 固定到 40 位的完整 commit SHA,并在行尾加上版本注释,比如 # v3.5.1,这样你既知道钉在哪个 commit,又能看出对应哪个发布版本。它不只是改 workflow 文件,还能处理 README 这类文本文件里的 action 引用,这对写文档的团队有用。适合谁?那些把安全审计当回事、又不想手动去改几十个文件的工程团队。
运行机制:本地编辑,GitHub API 负责查版本
pinact 的工作方式很直接。默认情况下,pinact run 会扫描 .github/workflows/*.yml、action.yml 等路径,找到所有 uses: 语句,然后调用 GitHub API 获取对应 action 的 releases 和 tags,算出最新的稳定版本和它的 SHA,再把文件里的引用替换成 SHA 加注释。如果你只想要检查而不修改,用 -check 或 -fix=false,它只报告哪些行没钉住。有个重要细节:pinact 默认会调用 GitHub API,所以文档明确建议你传一个 token,否则容易撞上 rate limit。如果你不想联网,可以用 -no-api,但那样它只能做语法层面的检查,也就是看引用是不是 40 位 SHA,没法帮你补全。
从安装到第一次运行:命令和配置项
安装方式在 INSTALL.md 里,但 README 没给具体命令,你可以用 go install 或者从 GitHub Releases 下载二进制。基本用法是 pinact run,后面可以跟文件路径,不跟就扫默认路径。想只检查不修改,pinact run -check。想更新到最新版本,pinact run -update。想设置冷却期,比如 7 天内发布的版本不采用,用 -min-age 7,也可以写在配置文件 .pinact.yml 里,结构是 min_age: { value: 7 },或者按规则写 rules: [{ min_age: 0, conditions: [{ expr: 'ActionRepoOwner == "suzuki-shunsuke"' }] }]。环境变量 PINACT_MIN_AGE 也可以。配置文件里还有 always: true 这个键,控制是否每次运行都验证当前版本是否满足冷却期,默认是 false,因为每次都查会浪费 API。
验证版本注释:一个容易被忽略但很实用的功能
pinact 有一个 -verify-comment 选项,对应文档里的 codes/001。它的作用是检查 SHA 后面的版本注释是否与实际版本一致。比如你钉了 actions/checkout@83b7061... 但注释写的是 # v3,而实际是 v3.5.1,pinact 会报错。这看起来是小事,但在多人协作的仓库里,注释不一致会导致审计时无法快速判断这个 SHA 属于哪个 release。文档还提到了 codes/005,要求 SHA 固定的 action 必须有版本注释。这两个功能结合起来,能让 workflow 文件的元数据更可信。不过注意,这个验证需要访问 GitHub API,离线模式下没法做。
分支固定与最小发布年龄:安全策略的两种补充
默认情况下,pinact 不碰 main 或 master 这种分支引用,但你可以用 --branch-to-tag 指定正则,比如 --branch-to-tag '^main$',它会把这个分支转换成最新的稳定 tag 的 SHA。文档提醒,正则用部分匹配,所以短分支名最好加 ^...$ 锚定,避免误匹配 mainline。另一个是 --min-age,它有两个作用:更新时过滤掉太新的版本,以及验证当前版本是否足够老。对于 tag,它检查 release 的 PublishedAt;对于纯 tag,它需要额外调一次 API 看 commit 的 Committer.Date。这意味着如果你用 -min-age 做验证,API 调用量会上升。这个设计是合理的,但你要有心理准备,CI 里跑 pinact 可能会比想象中慢。
SARIF 输出和 reviewdog 集成
pinact 支持 --format sarif,把检查结果输出成 SARIF 格式。这对接 reviewdog 或者 GitHub 的 Code Scanning 很方便。README 里明确提到 reviewdog,说明作者考虑了 CI 场景。你可以在 workflow 里跑 pinact run -check --format sarif,然后把输出喂给 reviewdog,这样 PR 评论里就能直接看到哪些 action 没钉住。这个功能对大型团队很有吸引力,因为代码审查时不需要每个人手动去看 workflow diff。注意,SARIF 输出只针对检查模式,不是编辑模式,所以它和 -update 是互斥的。
限制和替代方案:什么时候它不合适
pinact 的一个明显限制是它依赖 GitHub API,离线模式下只能做语法检查,没法真正钉版本。另外,它默认不处理分支引用,如果你刻意想保持 main 这种写法,它不会管,但如果你用了 --branch-to-tag,它会把分支改成 tag 的 SHA,这可能会改变行为,因为分支和 tag 对应的 commit 可能不同。还有一个问题是,对于 composite action 文件,它支持,但你需要确认你的 action.yml 里的 uses 语法是否符合它的解析规则。替代方案方面,有一个思路是直接用 Dependabot 来更新 actions,但 Dependabot 不会强制把版本改成 SHA,它通常保持 tag 形式。另一个是 actionlint,但它主要做语法和 schema 检查,不负责钉版本。pinact 的独特之处在于它把“固定到 SHA”和“版本注释”作为一等公民,并且有 -update 和冷却期机制,这是很多其他工具没有的。
维护与许可证
pinact 用 Go 编写,许可证是 MIT,这意味着你可以自由使用和修改,只要保留版权声明。仓库最后推送是 2026 年 7 月,最近发布了 v4.1.1,说明维护是活跃的。升级方面,由于是 CLI,升级成本取决于你如何安装。如果通过 go install,升级就是重新执行命令;如果下载二进制,需要替换文件。配置文件 .pinact.yml 的格式在 v4 里应该保持稳定,但建议升级前查看 release notes,特别是 v3 到 v4 是否有破坏性变更。文档里提到 pinact >= v3.10.0 支持 --branch-to-tag,所以如果你用旧版本,需要升级才能用这个功能。
编辑结论
如果你的仓库里有很多 workflow 文件,并且你希望把 actions 固定到完整 SHA 而不是 tag 或分支,pinact 值得一试。它特别适合那些已经用 reviewdog 做代码审查、或者想接入 GitHub Code Scanning 的团队,因为 SARIF 输出可以直接对接。但如果你只是偶尔改几个 workflow,或者你的 actions 更新频率很高,那每次运行 -update 都可能引入大量变动,反而增加审查负担。另外,如果你们还在用 GitHub Enterprise Server,需要确认 pinact 对 GHES 的支持方式(README 里有专门文档)。在正式采用前,先在一个测试仓库里跑一遍 pinact run -check -no-api,确认它不会误改你现有的格式,再决定是否纳入 CI。pinact 的维护者 suzuki-shunsuke 还维护着其他开源工具,仓库活跃度看起来不错,但具体版本兼容性需要查看 CHANGELOG。
社区笔记