SJ blog
devops
A

信頼度ランク

S 公式ソース確認済み
A 成功実績多数・失敗例少数
B 賛否両論
C 動作未確認・セキュリティリスク高
Z 個人所感

GitHub Actionsでハマりやすい落とし穴5選

GitHub Actionsの運用でよく踏む罠を5つ厳選。キャッシュ設計・GITHUB_TOKENの権限・タイムアウト・インジェクション・concurrencyの使い方を解説します。

一言結論

GitHub Actionsの障害の多くはキャッシュキー設計の甘さ・GITHUB_TOKENの過剰権限・concurrency未設定の3点に集中しており、これを最初から正しく設定するだけでCI/CDの信頼性が大きく向上する。

1. キャッシュキーにロックファイルのハッシュを含めていない

package-lock.json が変わってもキャッシュが再利用され、古い依存関係でビルドされる。

# ❌ キャッシュが永遠に使い回される
- uses: actions/cache@v4
  with:
    key: node-modules

# ✅ ロックファイルのハッシュをキーに含める
- uses: actions/cache@v4
  with:
    path: ~/.npm
    key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
    restore-keys: |
      ${{ runner.os }}-node-

restore-keys をフォールバックとして設定しておくと、完全一致しない場合に近いキャッシュを使ってくれる。

2. GITHUB_TOKEN の権限が広すぎる

デフォルト設定ではリポジトリの読み書き権限が広く付与されている。最小権限の原則を守る。

# ✅ ワークフローレベルで必要な権限だけを明示する
permissions:
  contents: read       # コードの読み取りのみ
  pull-requests: write # PRへのコメントのみ

jobs:
  lint:
    runs-on: ubuntu-latest
    # jobレベルでさらに絞ることもできる
    permissions:
      contents: read

リポジトリの Settings → Actions → General → Workflow permissions でデフォルトを Read repository contents に設定しておくと安全。

3. timeout-minutes を設定していない

デフォルトは 360分(6時間)。ハングしたジョブが課金され続ける。

jobs:
  build:
    runs-on: ubuntu-latest
    timeout-minutes: 15   # ジョブ全体のタイムアウト

    steps:
      - name: Install
        timeout-minutes: 5  # ステップ個別にも設定できる
        run: npm ci

      - name: Test
        timeout-minutes: 10
        run: npm test

CI が「なぜか1時間以上かかっている」という状況を防げる。

4. run ステップへのインジェクション

PRのタイトルやブランチ名を直接 ${{ ... }} で展開すると、攻撃者がコードを挿入できる。

# ❌ PRタイトルに "; rm -rf /" のような文字列を仕込まれる可能性
- run: echo "PR title: ${{ github.event.pull_request.title }}"

# ✅ 環境変数を経由させる(シェルがエスケープしてくれる)
- env:
    PR_TITLE: ${{ github.event.pull_request.title }}
  run: echo "PR title: $PR_TITLE"

外部からの入力(PRタイトル・ブランチ名・issueボディ等)はすべて環境変数経由にする。

5. concurrency で重複実行を防いでいない

同じブランチに連続でpushすると、古いワークフローと新しいワークフローが並走し、デプロイが競合する。

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true  # 同じブランチの古いジョブをキャンセル

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - run: npm run deploy

cancel-in-progress: true で古いジョブが自動キャンセルされるため、最新のコードだけがデプロイされる。本番デプロイのワークフローでは false にして競合を防ぐほうが安全な場合もある。


参考: GitHub Actions 公式ドキュメント / GitHub Actions Security Hardening