GitHub Actionsのワークフロー依存関係ロックファイルを試す
X-tech推進本部 黒澤先日、GitHub Actionsのワークフロー依存関係ロックファイルがパブリックプレビューになりました。本記事では、ロックファイルの生成方法と、タグやブランチが指すコミットが変更された場合の挙動を紹介します。なお、ロックファイルは記事執筆時点ではプレビュー段階であり、今後仕様が変更される可能性があります。
背景
GitHub Actionsでは、別のリポジトリで公開されているアクションを呼び出すことができます。例えば、https://github.com/actions/checkoutで提供されているアクションを呼び出すには次のように記述できます。
uses: actions/checkout@v7.0.1
@の後ろはバージョン指定を表す文字列で、ブランチやタグ、コミットハッシュなどを記述できます。v7.0.1はタグです。
ブランチやタグを記述した場合、GitHub Actionsは、実行のたびにブランチやタグが指し示すコミットを解決して、実行コミットを決定します。記事執筆時点ではv7.0.1が指し示すコミットは3d3c42e5aac5ba805825da76410c181273ba90b1でしたので、以下のように記述しても、呼び出し内容は同じになります。
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
しかし、Gitではタグやブランチが指し示すコミットは後から変更できます。動作確認時にタグやブランチが指し示していたコミットと、実行時に解決されるコミットが異なることがありえます。
実際に、このことを悪用したサイバー攻撃が発生しています(Palo Alto Networks Unit 42の調査)。
tj-actions/changed-filesのワークフローではtj-actions/eslint-changed-filesアクションを呼び出していたtj-actions/eslint-changed-filesアクションはreviewdog/action-setupアクションをタグ指定で呼び出していた- その後、
reviewdog/action-setupのタグが、認証情報などを盗む悪意あるコミットを指すように付け替えられた tj-actions/changed-filesのワークフロー実行時に、付け替えられたコミットのreviewdog/action-setupが実行され、認証情報が盗まれた
ワークフローで直接呼び出すアクションのバージョンをコミットに固定しても、推移的な依存関係にタグやブランチの指定が残っていれば、コミットの付け替えによる攻撃に脆弱です。
このことはGitHubも認識しており、2026年3月に公開されたGitHub Actionsのセキュリティロードマップにおいて、ロックファイルの開発がアナウンスされました。
ロックファイルでは、直接呼び出すアクションに加え、推移的な依存関係にあるアクションも特定のコミットに固定できます。ただし、再利用可能なワークフロー内の依存関係は、そのワークフローが定義されているリポジトリ側で管理されます。
導入方法
すでにGitHubよりロックファイルの導入ガイドが公開されていますので、最新情報や詳細な情報はそちらをご確認ください。執筆時点での導入方法は次のとおりでした。
ロックファイルは、GitHub CLI(ghコマンド)の拡張機能を使って生成します。
まずは拡張機能をインストールします。
gh extension install github/gh-actions-lock
ロックファイルを生成するには次のコマンドを実行します。
gh actions-lock
実行に成功すると.github/workflows/actions.lockが生成されますので、リポジトリにコミットします。
ワークフローの依存関係を追加したり削除したりした場合も、同じコマンドでロックファイルを更新できます。すでに記録されているコミットは変更されません。タグが別のコミットを指すようになっている場合などは、コマンドが失敗します。
ロックファイルの内容
生成されたロックファイル(.github/workflows/actions.lock)には、各ワークフローが依存するアクションと、各アクションのコミットなどが記録されます。lint-node.ymlがactions/checkoutを呼び出している場合には次のようになりました。
# This file is machine-generated by `gh actions-lock`.
# Do not edit by hand; run `gh actions-lock` to update.
# Docs: https://gh.io/actions-lockfile
version: 'v0.0.3'
workflows:
'.github/workflows/lint-node.yml':
- 'actions/checkout@v7.0.1'
dependencies:
'actions/checkout@v7.0.1':
ref: 'v7.0.1'
commit: 'sha1-3d3c42e5aac5ba805825da76410c181273ba90b1'
owner_id: 44036562
repo_id: 197814629
なお、既存のワークフローファイルでバージョンをコミットハッシュで指定している場合、同じコミットを指すタグに書き換えられます。
具体的には、次のような指定が
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
次のように書き換えられました。
uses: actions/checkout@v7.0.1
GitHub Actionsの設定やghalintなどのチェックツールで、コミットハッシュによる指定を必須にしている場合には、調整が必要です。
また、アクションが別のアクションを呼び出す場合(推移的依存関係)も、呼び出し先のアクションを含めてロックファイルに記録されます。次の例ではlint-github-actions.ymlがreviewdog/action-shellcheckを呼び出しており、reviewdog/action-shellcheckがreviewdog/action-setupを呼び出しています。ロックファイルには、いずれのアクションも記録されています。
workflows:
'.github/workflows/lint-github-actions.yml':
- 'actions/checkout@v7.0.1'
- 'reviewdog/action-shellcheck@v1.30.0'
dependencies:
'actions/checkout@v7.0.1':
# 略
'reviewdog/action-setup@e04ffabe3898a0af8d0fb1af00c188831c4b5893':
ref: 'v1.3.2'
commit: 'sha1-e04ffabe3898a0af8d0fb1af00c188831c4b5893'
owner_id: 48765237
repo_id: 286005511
'reviewdog/action-shellcheck@v1.30.0':
ref: 'v1.30.0'
commit: 'sha1-5ebd09ddbe2ebb471646ce234c6c8dd18663ca7c'
owner_id: 48765237
repo_id: 211116201
uses:
- 'reviewdog/action-setup@e04ffabe3898a0af8d0fb1af00c188831c4b5893'
ロックファイルに記録されたコミットとブランチやタグが指し示すコミット
ロックファイルの対象になっているワークフローでは、refが指しているコミットではなく、commitに記録したコミットが実行されます。ブランチやタグが指し示すコミットが変わっても、記録したコミットへ到達できる限り、実行されるコミットは変わりません。
- ロックファイルに、その時点のブランチの先頭コミットが記録される
- ブランチにコミットが追加されると、先頭コミットが移動し、記録済みコミットは、新しい先頭コミットの先祖としてブランチに残る
- 実行時には、新しい先頭コミットではなく、ロックファイルに記録済みのコミットが呼び出される
タグが付け替えられた場合も同じです。付け替え後のコミットから記録済みのコミットへ到達できれば、付け替え後のコミットではなく、記録済みのコミットが呼び出されます。
ただし、commitに記録したコミットが、refが現在指しているコミットの履歴に含まれていない(refからcommitに到達できない)場合、実行がブロックされます。
例えば、actions/checkoutでは、当初v6系列をmainブランチで開発していました。v7系列の開発開始に伴い、mainブランチはv7系列用に切り替わり、v6系列の更新はmainから分岐したreleases/v6ブランチで行われるようになりました。
refがv7.0.1の場合、その先祖であるv6.0.0のコミットをcommitに指定しても、実行はブロックされません。ロックファイルはgh actions-lockで生成し、通常は手で編集しませんが、検証のためにcommitを書き換えたものを示します。
dependencies:
'actions/checkout@v7.0.1':
ref: 'v7.0.1'
# v6.0.0タグが指し示すコミット
commit: 'sha1-1af3b93b6815bc44a9784bd300feb67ff0d1eeb3'
owner_id: 44036562
repo_id: 197814629
一方、releases/v6上にあり、v7.0.1の先祖ではないv6.1.0のコミットを指定すると、実行はブロックされます。
dependencies:
'actions/checkout@v7.0.1':
ref: 'v7.0.1'
# v6.1.0タグが指し示すコミット
commit: 'sha1-d23441a48e516b6c34aea4fa41551a30e30af803'
owner_id: 44036562
repo_id: 197814629
表示されたメッセージは次のとおりです。
Lockfile pin d23441a48e516b6c34aea4fa41551a30e30af803 for
actions/checkoutdoes not match refv7.0.1. Ensure the pinned commit exists on the expected branch or tag
繰り返しになりますが、呼び出し先のタグやブランチが別のコミットへ付け替えられても、新しいコミットが実行されることはありません。記録したコミットへ到達できる場合は記録したコミットが実行され、到達できない場合は呼び出し側のワークフローがブロックされます。
まとめ
GitHub Actionsのロックファイルは推移的な依存関係を含めて、アクションのバージョンを固定できます。現時点ではまだプレビュー段階ですので、小規模な非本番リポジトリで動作検証を始めてみてはいかがでしょうか。