はじめに
GitHub Actionsでこんなコードを書いたことはありませんか?
- uses: actions/checkout@v4
実はこの書き方、サプライチェーン攻撃のリスクがあります。
- uses: actions/checkout@v4.1.1 # こちらの方が安全
「え、なんで?」と思った方、この記事で詳しく解説します。
この記事でわかること
- Gitのタグの仕組み
- なぜメジャーバージョン指定(
@v4)が危険なのか - サプライチェーン攻撃の具体的なシナリオ
- パッチバージョン指定(
@v4.1.1)が安全な理由
先に結論
| 指定方法 | 例 | 安全性 |
|---|---|---|
| メジャーバージョン | @v4 |
⚠️ リスクあり |
| パッチバージョン | @v4.1.1 |
✅ 安全 |
| コミットハッシュ | @a]5ac7e... |
✅ 最も安全 |
理由:Gitのタグは移動できるから
これだけだとピンとこないと思うので、詳しく説明していきます。
前提知識:Gitのタグは「移動できる」
まず、Gitのタグの仕組みを理解しましょう。
タグ = コミットを指すポインタ
Gitのタグは、特定のコミットを指す「ポインタ」です。
コミット履歴:
abc123 ← v4.1.1 タグがここを指している
↓
def456
↓
ghi789 ← v4 タグがここを指している
重要:タグは移動できる!
多くの人が見落としがちなのが、タグは別のコミットに移動できるということです。
# タグを別のコミットに移動するコマンド
git tag -f v4 <新しいコミットハッシュ>
これが後述するサプライチェーン攻撃の原因になります。
GitHub Actionsのバージョン解決の仕組み
GitHub Actionsでアクションを指定すると、内部では以下のように動作します。
@v4 を指定した場合
uses: actions/checkout@v4
- GitHub Actionsが
actions/checkoutリポジトリにアクセス -
v4タグが指すコミットを確認 - そのコミットのコードを取得して実行
ここで重要なのは、タグが移動されると、自動的に新しいコードが実行されるということです。
@v4.1.1 を指定した場合
uses: actions/checkout@v4.1.1
- GitHub Actionsが
actions/checkoutリポジトリにアクセス -
v4.1.1タグが指すコミットを確認 - そのコミットのコードを取得して実行
パッチバージョンのタグは通常は移動されないため、常に同じコードが実行されます。
サプライチェーン攻撃のシナリオ
では、実際にどのような攻撃が行われるのか見てみましょう。
攻撃の流れ
┌─────────────────────────────────────────────────────────────┐
│ 1. 攻撃者が actions/checkout リポジトリに侵入 │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 2. 悪意のあるコードを含むコミットを作成 │
│ (例:環境変数やシークレットを外部に送信するコード) │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 3. v4 タグを悪意のあるコミットに移動 │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 4. @v4 を指定しているワークフローが │
│ 自動的に悪意のあるコードを実行してしまう! │
└─────────────────────────────────────────────────────────────┘
図解:タグ移動による攻撃
正常時のリポジトリ状態:
v4 → コミット abc123 (安全なコード ✅)
v4.1.1 → コミット def456 (安全なコード ✅)
リポジトリ侵害後:
v4 → コミット xyz789 (悪意のあるコード 🚨) ← タグが移動された!
v4.1.1 → コミット def456 (安全なコード ✅) ← 変更なし
この状態で各ワークフローが実行されると:
| 指定方法 | 実行されるコード | 結果 |
|---|---|---|
@v4 |
xyz789(悪意のあるコード) | 🚨 攻撃成功 |
@v4.1.1 |
def456(安全なコード) | ✅ 安全 |
なぜパッチバージョン指定が安全なのか
理由1:タグが移動されにくい
メジャーバージョンのタグ(v4)は、新しいパッチがリリースされるたびに更新されることが一般的です。
一方、パッチバージョンのタグ(v4.1.1)は、一度作成されたら通常は移動されません。
理由2:変更がコードレビューで検知できる
パッチバージョンを指定している場合、バージョンを上げるにはコードの変更が必要です。
# 変更前
- uses: actions/checkout@v4.1.1
# 変更後(この変更はPRで確認できる)
- uses: actions/checkout@v4.1.2
この変更はプルリクエストのレビューで確認できるため、不審な変更を検知しやすくなります。
理由3:実行内容が予測可能
常に同じコミットが実行されるため、CI/CDパイプラインの再現性が高まります。
よくある誤解
❌「最新バージョンに脆弱性があると攻撃される」
これは誤解です。
パッチバージョン指定で防げるのは、リポジトリ侵害によるタグ移動攻撃です。
脆弱性対応は別の話で、既知の脆弱性がある場合は手動でバージョンを更新する必要があります。
パッチバージョン指定の目的
| 目的 | 対応できる? |
|---|---|
| タグ移動による意図しないコード実行を防ぐ | ✅ できる |
| 脆弱性を自動的に修正する | ❌ できない(手動更新が必要) |
実践:安全なワークフローの書き方
Before(危険)
name: CI
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4 # ⚠️ 危険
- uses: actions/setup-node@v4 # ⚠️ 危険
- uses: hashicorp/setup-terraform@v3 # ⚠️ 危険
After(安全)
name: CI
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4.1.1 # ✅ 安全
- uses: actions/setup-node@v4.0.2 # ✅ 安全
- uses: hashicorp/setup-terraform@v3.0.0 # ✅ 安全
さらに安全にするなら:コミットハッシュ指定
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
コミットハッシュを直接指定すれば、タグの移動に一切影響されません。ただし可読性が下がるため、コメントでバージョンを記載しておくと良いでしょう。
Dependabotで自動更新する
パッチバージョンを固定すると、セキュリティアップデートを手動で追う必要があります。
Dependabotを使えば、新しいバージョンがリリースされたときに自動でPRを作成してくれます。
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
これにより:
- パッチバージョン指定でタグ移動攻撃を防ぎつつ
- Dependabotでセキュリティアップデートを自動追跡
という安全な運用が可能になります。
まとめ
| ポイント | 内容 |
|---|---|
| 問題 | Gitのタグは移動可能なため、メジャーバージョン指定だとタグ移動攻撃を受ける |
| 対策 | パッチバージョン(@v4.1.1)またはコミットハッシュを指定する |
| 利点 | 意図しないコード実行を防ぎ、変更をコードレビューで確認できる |
| 注意 | 脆弱性対応は別途必要(Dependabotの活用を推奨) |
サプライチェーン攻撃は近年増加しており、2021年のCodecov事件など、実際に被害が発生しています。
たった数文字の変更で大きなリスクを軽減できるので、ぜひ今日から実践してみてください!