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。

一次可回復的五步試點:

  1. 盤點:列出直接 Action、內層 Composite Action,以及跨儲存庫 Reusable Workflow,確認被呼叫端的維護責任。
  2. 生成:在測試分支使用固定版本工具,檢查 Workflow、action.yml、actions.lock 的完整差異。
  3. 核對政策:以鎖定 Commit 逐一比對 Allowed Actions,正常進行 Code Review。
  4. 兩層校驗:先用 --verify-local 查覆蓋,再用 --verify 查來源身分。not-pinned、stale、local-action 回頭檢查路徑及 Diff;repo-replaced、unreachable-pin 先調查上游歷史,不要直接合併。
  5. 保留回退路徑:若解析或政策阻擋 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 重新指向實驗。預覽權限以實際帳戶方案與設定為準。