GitHub Actions Workflow Lockfiles:只鎖 uses@SHA,為什麼還不夠?
GitHub Actions Workflow Lockfiles:只鎖 uses@SHA,為什麼還不夠?
將 GitHub Actions 中的 uses: some-org/action@v2 改為完整 Commit SHA,的確能固定這個入口的程式碼。不過,如果它是一個 Composite Action,內部又引用 example-org/upload@v1 呢?外層 Commit 沒變,內層 Tag 卻仍可能移動。光看 Workflow YAML,未必看得出整條第三方 Action 依賴鏈。
GitHub 在 Workflow Lockfiles 公開預覽公告中推出 .github/workflows/actions.lock,讓直接與傳遞 Action 依賴都能記錄鎖定的 Commit 及來源儲存庫身分。
已經全面使用 uses@SHA,還需要導入 Lockfile 嗎?先看依賴圖,再決定是否遷移。 新機制真正補上的是傳遞依賴清單與儲存庫身分驗證,不是對同一個 SHA 重複加鎖。
| 目前的 CI 情況 | 建議 | 理由 |
|---|---|---|
| 直接呼叫的 Action 已用完整 SHA 固定,引用層級簡單 | 先盤點,暫緩整個儲存庫切換 | 可能沒有足夠收益抵銷維護成本 |
Composite Action 又呼叫 @v1 或 @main | 選一條非關鍵 Workflow 試點 | 可以鎖住傳遞 Action 依賴 |
| 多個儲存庫共用 Reusable Workflow | 先釐清呼叫端與被呼叫端的責任 | 內部 Action 由定義 Reusable Workflow 的儲存庫負責 |
| 組織已設定 Allowed Actions 或 SHA 固定策略 | 先核對政策,再進行試點 | 鎖定檔上線後可能以 Commit SHA 比對允許清單 |
這些情況可以重疊。以下以 gh-actions-lock v0.2.0 的固定原始碼與官方預覽文件為準。
直接固定 SHA,不等於固定整張 Action 依賴圖
假設 ci.yml 用完整 SHA 呼叫 example-org/build,但它的 action.yml 內部繼續執行 example-org/upload@v1。外層來源已固定,下游版本標籤仍可能更新。
| 依賴入口 | 直接 uses@SHA | 納入 Workflow Lockfile 後 |
|---|---|---|
| Workflow 直接引用的第三方 Action | 固定該筆引用 | 記錄 Commit 及儲存庫身分 |
| Composite Action 內的其他 Action | 不會自動鎖住內層 Tag | 包含傳遞 Action 依賴 |
| 跨儲存庫 Reusable Workflow | 可固定呼叫入口 | 呼叫端鎖住呼叫;內層 Action 由被呼叫儲存庫管理 |
同儲存庫 uses: $/… | 跟隨目前儲存庫 Commit | 本身具備固定語意,不用額外 Pin |
run: 下載腳本或可變容器 Image Tag | 不在該 Action Pin 範圍 | 仍無法僅靠 Action Lockfile 管控 |
這裡最重要的是跨儲存庫責任邊界。GitHub 官方概念說明規定,鎖定檔涵蓋該儲存庫所定義的 Workflow;外部 Reusable Workflow 裡使用的 Action,則由定義該 Reusable Workflow 的儲存庫自己的 Lockfile 負責。業務儲存庫固定共享部署流程的入口,不代表已經稽核了對方每一層內部 Action。
actions.lock 裡有哪些內容
workflows 將 Workflow 路徑對應到依賴 Pin;dependencies 記錄 ref、commit、owner_id、repo_id,遇到 Composite Action 的下一層呼叫還可能有 uses。下面是 官方格式儲存庫固定提交的 benchV003 測試樣本,完整呈現 v0.0.3 的 YAML 結構。多次出現的 a/b 雜湊值是測試用佔位資料,不是正式 Runner 的鎖定結果:
version: v0.0.3
workflows:
.github/workflows/ci.yml:
- actions/checkout@v4
- actions/setup-go@v5
- actions/cache@v4
dependencies:
actions/checkout@v4:
hostname: github.com
ref: v4
commit: sha1-11bd71901bbe5b1630ceea73d27597364c9af683
owner_id: 44036562
repo_id: 197814629
actions/setup-go@v5:
hostname: github.com
ref: v5
commit: sha1-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
owner_id: 44036562
repo_id: 249058325
actions/cache@v4:
hostname: github.com
ref: v4
commit: sha1-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
owner_id: 44036562
repo_id: 251882839
uses:
- actions/checkout@v4
這份樣本會明列 hostname: github.com;實際在預設 github.com 主站生成的鎖定檔可能省略該欄位。dependencies 底下的 uses 顯示內層 Action 關係,而 workflows 顯示各條 Workflow 需要的 Pin,不是同一種清單。
固定版 state_marshal.go 原始碼實際會寫出這些欄位:
a := file.Dependencies[k]
entry := &yaml.Node{Kind: yaml.MappingNode}
// 此处省略对 hostname 的处理分支
if a.Ref != "" {
addQuotedField(entry, "ref", a.Ref)
}
if a.Commit != "" {
addQuotedField(entry, "commit", a.Commit)
}
addIntField(entry, "owner_id", a.OwnerID)
addIntField(entry, "repo_id", a.RepoID)
Commit SHA 指出 Git 物件,數字化的 owner_id 和 repo_id 則可確認名稱背後的儲存庫身分;即使發生更名或轉移,檢查也不只依賴可讀名稱。官方概念頁的示例仍標示 version: v0.0.2,但這次檢查的 v0.2.0 Release 與格式 README都對應 v0.0.3。CLI 版本與鎖定檔 Schema 版本並不相同。
遷移前,先檢查實際會改動哪些檔案
我會在測試分支先挑一條可回復的非關鍵 Workflow。假定 GitHub CLI 已安裝、登入,並有權讀取相關儲存庫,可用下列固定版本命令:
# 固定本文检查的扩展版本
gh extension install github/gh-actions-lock --pin v0.2.0
gh actions-lock .github/workflows/ci.yml
# 同时检查 workflow、本地 action.yml 的修改与新文件
git status --short
git diff
cat .github/workflows/actions.lock
# 只校验试点 Workflow,避免未迁移的其他文件干扰
gh actions-lock --verify .github/workflows/ci.yml
單檔驗證語法有固定原始碼支援:root.go 接受 Workflow 路徑;verify.go 與上游測試確認校驗使用此範圍。共用 actions.lock 仍須是有效格式,但不會因驗證某一條 Workflow 就自動納入其他尚未遷移的檔案。
工具不只是新增 actions.lock,也可能把版本標籤縮至更精確的版本,或將有效的本地 Action ./actions/build 改為 $/actions/build:
steps:
- - uses: ./actions/build
+ - uses: $/actions/build
$/ 會綁定目前執行中的儲存庫 Commit。不合法的 ./ 路徑不會因此被修好。新建但尚未 git add 的 actions.lock 也不會出現在一般 git diff 裡,應一起檢查 git status --short。
組織的 Allowed Actions 政策也是遷移風險。Workflow 使用 Lockfile 後,相關允許清單會依鎖定的 Commit SHA比對;原先寫成 actions/checkout@v6 的規則可能失配。做法是對照實際核准 Commit 調整政策,而非直接開放全部第三方 Action。
離線與線上校驗要分開:
gh actions-lock --verify-local .github/workflows/ci.yml
gh actions-lock --verify .github/workflows/ci.yml
--verify-local 只檢查本地鎖定覆蓋率,不會查 GitHub 上的來源身分;--verify 才會進行線上信任驗證,適合用於 PR 門禁。全面遷移完成後再用不帶路徑的 gh actions-lock --verify 做整個儲存庫的檢查;過早執行可能因尚未納入的 Workflow 或無用 Pin 而報錯。
需要排查時,可加 --json=valid,findings。退出碼 1 表示仍有阻斷 Finding,2 表示命令、I/O 或網路錯誤。原始碼定義的 Finding 類型包括 not-pinned、stale、local-action、repo-replaced 和 unreachable-pin。
一次可回復的五步試點:
- 盤點:列出直接 Action、內層 Composite Action,以及跨儲存庫 Reusable Workflow,確認被呼叫端的維護責任。
- 生成:在測試分支使用固定版本工具,檢查 Workflow、
action.yml、actions.lock的完整差異。 - 核對政策:以鎖定 Commit 逐一比對 Allowed Actions,正常進行 Code Review。
- 兩層校驗:先用
--verify-local查覆蓋,再用--verify查來源身分。not-pinned、stale、local-action回頭檢查路徑及 Diff;repo-replaced、unreachable-pin先調查上游歷史,不要直接合併。 - 保留回退路徑:若解析或政策阻擋 CI,可撤銷測試分支的變更;試點穩定後才擴大到整個組織。
Lockfile 建立後,怎麼更新依賴
首次鎖定的 Commit 取決於原始 uses 與解析結果。v0.2.0 的 release cooldown 並沒有固定七天預設值:有設定時優先使用 .github/dependabot.yml 中 github-actions 正數的 cooldown.default-days,否則採用使用者設定檔的 cooldown_days;都沒有時不進行發行年齡過濾,對三天內發行的新 Tag 僅產生非阻斷性的 fresh-tag 提醒。參照設定解析與新標籤提醒。因此要對照生成檔裡的 ref、commit 和 Workflow Diff,不能想當然耳。
工具採用 sticky pins:一般再次執行 gh actions-lock 不會因上游 main 或 v3 移動就自動覆寫既有 Commit。要接受更新應明確使用 gh actions-lock --relock,並審查每一筆變更。如果上游改寫歷史造成舊 Commit 不可達,--relock --accept-moved 屬於獨立的信任決策,不能只為了讓 CI 轉綠就跳過調查。
Dependabot 可為已納入鎖定檔的 Workflow 維護更新,但不會自動替新 Workflow 完成首次納入。GitHub 組織政策指南建議先小範圍試點,再以 CODEOWNERS 審查 actions.lock,最後才考慮 Require lockfile。請先確認帳戶計畫與權限;固定版 README 也註明此預覽尚不支援 GitHub Enterprise Server,不能與 Enterprise Cloud 混為一談。
Lockfile 能確認執行的程式碼身分,卻不能判定程式碼有無漏洞。run: 下載的腳本、未鎖 Digest 的容器、套件依賴與 CI Token 權限都要另外處理。一個值得推廣的試點,應該讓團隊能回答:究竟新增鎖住哪些內層 Action、誰審查了來源儲存庫身分、共享 Reusable Workflow 的內部依賴誰負責?這比單純新增一個產生檔更有價值。另一個掃描與阻擋合併的差異,可以參考 GitHub AI Scan 技術分析。
附註/證據時間:GitHub 於 2026 年 10 月 14 日宣布 Workflow Lockfiles Public Preview。本篇核對 gh-actions-lock v0.2.0 固定提交 d5dca22a06ff8590d50b41612038996800cfb336 及格式儲存庫提交 ac158834765237ee94c36606cfc7d87316aa49ab,檢查日期為 2026 年 10 月 18 日。YAML 是上游測試樣本,含合成雜湊值;本文並未實際執行 Runner、CLI 遷移或 Tag 重新指向實驗。預覽權限以實際帳戶方案與設定為準。