GitHub Actions セキュリティ実践:tj-actions インシデントから学ぶ3つの重要な防御策
2025年3月、ある GitHub Advisory が私の認識の境界を打ち破りました。tj-actions/changed-files——私が3年間使い続けてきたこの Action が、CVE-2025-30066 としてマークされたのです。23,000 以上のリポジトリが、一夜にして Secrets 漏洩リスクに晒されました。
さらに背筋が凍ったのは攻撃手法でした。攻撃者はまずあるプロジェクトの PAT を盗み、tj-actions のバージョンタグを改ざんして、本来安全なコードを悪意あるスクリプトに向けました。その Secrets——AWS キー、データベースパスワード、API Token——が、知らず知らずのうちに公開ログに流れていったのです。
正直なところ、当時はかなり動揺しました。自分で設定した CI/CD フローが、攻撃者の踏み台になっていたのですから。すぐにすべてのリポジトリを確認し、Action バージョン参照、GITHUB_TOKEN 権限、監査ログ設定をチェックしました。丸一日かけて、これまで見落としていた細部がいかに多かったかを痛感しました。
この記事は、その「危機的瞬間」の後に整理した防御チェックリストです。サプライチェーン攻撃の手口、Secrets 管理の正しい姿勢、GITHUB_TOKEN 権限制御の詳細、そして GitHub 2026 セキュリティロードマップで事前に準備すべき新機能についてお話しします。
重要なのは:これらはすべて、今すぐ設定できることだということです。新機能のリリースを待つ必要はありません。
tj-actions インシデントから見る CI/CD サプライチェーン攻撃
攻撃はどう発生したか
まず、tj-actions/changed-files という Action について。これは GitHub で非常に人気があり——PR でどのファイルが変更されたかを検出するもので、多くの CI/CD フローで使用されています。私のいくつかのプロジェクトも、差分デプロイの判断に依存していました。
攻撃チェーンは概ねこのような流れでした:
攻撃者はまず reviewdog/action-setup プロジェクトから PAT(個人アクセストークン)を盗みました。この PAT にはリポジトリへの書き込み権限がありました。トークンを入手した攻撃者は、それを使って tj-actions リポジトリのバージョンタグを改ざんしました——本来安全なコードを指していた v45 タグを、密かに悪意あるロジックを埋め込んだ新しいコミットに向けたのです。
まだ uses: tj-actions/changed-files@v45 を使っていたプロジェクトは、何も知らずに悪意あるコードをプルしました。悪意あるスクリプトが行ったことは単純でした:CI/CD 環境内の Secrets をすべてログに出力する。ログは公開されるため、Secrets はこうして漏洩しました。
GitHub Advisory レポートによると、23,000 を超えるリポジトリが影響を受けました。Coinbase のセキュリティチームは後に、この攻撃が 70,000 人以上の顧客データに波及したことを明らかにしました。
私が踏んだ落とし穴:タグ参照 vs SHA 固定
自分のリポジトリを確認したとき、多くの場所でタグ参照を使っていることに気づきました:
# 誤った例:可変タグを使用
- name: Check changed files
uses: tj-actions/changed-files@v45
タグは生きています。リポジトリのメンテナ(または書き込み権限を取得した攻撃者)は、いつでもタグを新しいコミットに向けることができます。あなたが v45 を参照していると思っていても、実際に実行されるのは改ざんされた悪意あるコードかもしれません。
正しい方法は、完全な SHA で固定することです:
# 正しい方法:完全な SHA を使用
- name: Check changed files
uses: tj-actions/changed-files@6cbf527e7a7b6d61c4e7f25e5ce5f7b7c8f3c72a
SHA は死んでいます。あなたが能動的に参照を更新しない限り、実行されるのはそのコードであり、永遠に変わりません。
当時、私は修正しながら自分を責めていました:これは明らかに基本的なセキュリティ常識なのに、なぜずっと気づかなかったのかと。
サードパーティ Action の信頼コスト
tj-actions インシデントはもう一つの問題を浮き彫りにしました:サードパーティ Action に対する信頼があまりに安易であること。
適当な Action でも、スター数が多く、使っている人が多ければ、生産環境の CI/CD フローに直接組み込んでしまう。しかし、メンテナのセキュリティ意識はどうなのか? 彼らの PAT が盗まれない保証はあるのか?
GitHub 2026 セキュリティロードマップでは、一つの解決策が提示されています:ワークフローレベルの依存関係ロック。package-lock.json のように、すべての Action の SHA をロックファイルに固定します。ワークフローでは uses: tj-actions/changed-files@v45 と書いたままでも、ロックファイルが対応する SHA を記録しています。メンテナが Action を更新しても、ロックファイルは自動的に変わらない——あなたが能動的に審査し更新する必要があります。
この機能はまだリリースされていませんが、今から手動で SHA 固定を行い、定期的に監査することができます。
もう一つの提案は、サードパーティ Action の数を減らすことです。公式 Action で解決できるなら、サードパーティは使わない。例えば、ファイル検出は、GitHub 公式の actions/checkout とシェルスクリプトで実現でき、tj-actions に依存する必要はありません。
Secrets 管理の3つのレベルと応用実践
GitHub Secrets の3段階ストレージ
GitHub 組み込みの Secrets は3つのレベルに分かれています:組織レベル、リポジトリレベル、環境レベル。
組織レベルの Secrets は複数のリポジトリで共有でき、AWS キーやクラウドサービス Token などの共通認証情報の保管に適しています。リポジトリレベルの Secrets は現在のリポジトリでのみ使用でき、プロジェクト固有のデータベースパスワードなどに適しています。環境レベルの Secrets はより細かく、環境保護ルールと組み合わせられます——例えば、PR の審査が承認されてからでないと本番環境の Secrets にアクセスできないようにするなど。
ストレージ面では、GitHub は libsodium sealed box で暗号化しています。Secrets は書き込まれた後は読み出せず、ワークフロー実行時にのみ secrets コンテキストを通じてアクセスできます:
steps:
- name: Deploy to AWS
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
よくある間違いは、Secrets を直接シェルコマンドに埋め込むことです:
# 危険:Secrets がログに出力される可能性
- name: Configure AWS
run: aws configure set aws_access_key_id ${{ secrets.AWS_ACCESS_KEY_ID }}
Secrets の値に特殊文字が含まれていたり、コマンドの実行が失敗したりすると、ログに Secrets が露出する可能性があります。正しい方法は、環境変数を通じて渡すことです:
# 安全:環境変数を通じて渡す
- name: Configure AWS
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
run: aws configure set aws_access_key_id "$AWS_ACCESS_KEY_ID"
動的マスキング:::add-mask::
一部の機密データは、事前に保存された Secrets ではなく、実行時に生成されます。例えば、あるスクリプトが出力する一時 Token など。この場合、::add-mask:: で動的にマスクできます:
- name: Generate temporary token
run: |
token=$(generate-token.sh)
echo "::add-mask::$token"
echo "TOKEN=$token" >> $GITHUB_ENV
マスクされた値は、ログに *** と表示されます。ただし注意:マスクは値が出力される前に行われなければなりません。先に出力してからマスクしても、ログには露出してしまいます。
応用ソリューション:HashiCorp Vault OIDC 統合
プロジェクトのセキュリティ要件が高い場合、GitHub 組み込み Secrets では不十分かもしれません。長期認証情報が GitHub に存在すると、リポジトリが侵害された瞬間に Secrets は全滅します。
より良いソリューションは、HashiCorp Vault を使用し、OIDC(OpenID Connect)と組み合わせて認証情報なしアクセスを実現することです。原理は、GitHub Actions が Vault に「私は誰か」を証明し、Vault が検証した後に短期 Token を発行するというものです。これで、GitHub には長期認証情報が一切保存されなくなります。
設定手順は概ね以下の通りです:
ステップ1:Vault で OIDC ロールを設定
Vault は GitHub の OIDC プロバイダーを信頼する必要があります。どのリポジトリがどの Secrets を取得できるかを指定するロールを設定します:
resource "vault_jwt_auth_backend_role" "github_actions" {
backend = "jwt"
role_name = "github-actions-role"
bound_audiences = ["https://github.com/your-org"]
user_claim = "repository"
role_type = "jwt"
token_policies = ["ci-policy"]
token_ttl = "1h"
}
ステップ2:GitHub Actions で vault-action を使用
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Import Secrets from Vault
uses: hashicorp/vault-action@v2.4.0
id: vault
with:
url: https://vault.example.com:8200
role: github-actions-role
method: jwt
secrets: |
secret/data/ci/aws accessKey | AWS_ACCESS_KEY_ID ;
secret/data/ci/aws secretKey | AWS_SECRET_ACCESS_KEY
- name: Deploy with AWS credentials
run: |
echo "Accessing AWS with Vault-provided credentials"
aws s3 ls
このフローでは、Vault-action が GitHub 提供の OIDC Token で自動的に Vault に認証します。Vault が検証した後、Secrets を返し、環境変数に注入します。1時間後に Token は自動的に失効し、次回の実行で再取得します。
Azure Key Vault も同様の OIDC 統合をサポートしており、設定方法はほぼ同じで、azure/login Action と Azure Key Vault Secrets を組み合わせます。
GITHUB_TOKEN 権限制御の実践
GITHUB_TOKEN とは
GitHub Actions ワークフローが実行されるたび、GITHUB_TOKEN が自動的に付与されます。これは一時的な OAuth Token で、現在のリポジトリを操作するために使用されます——例えば、Release の作成、コードのプッシュ、PR へのコメントなど。
問題は:GITHUB_TOKEN のデフォルト権限が大きすぎることです。
旧バージョンの GitHub Actions では、GITHUB_TOKEN の読み書き権限はほぼ全開でした。ワークフローは自由にリポジトリの内容を変更し、ブランチを作成し、コードをプッシュできました。ワークフローが攻撃者に悪用されると(例えば PR を通じた悪意あるスクリプトなど)、GITHUB_TOKEN は攻撃者の武器になります。
permissions キーの完全な設定
GitHub は 2021 年 4 月に permissions キーを導入し、GITHUB_TOKEN の権限範囲を正確に制御できるようになりました。
基本構文はこのようになります:
permissions:
actions: read|write|none # Actions の管理
contents: read|write|none # リポジトリの内容
issues: read|write|none # Issue 操作
packages: read|write|none # GitHub Packages
pull-requests: read|write|none # PR 操作
security-events: read|write|none # セキュリティイベント報告
deployments: read|write|none # デプロイ状態
statuses: read|write|none # Commit 状態
重要なメカニズム:ワークフローで permissions キーを書くと、指定されていないすべての権限が自動的に none になります。これが「最小権限」のセキュリティ境界です。
ワークフローレベル vs ジョブレベルの権限
permissions は2つのレベルで記述できます:ワークフローレベル(グローバル)とジョブレベル(ローカル)。
ワークフローレベルの権限はすべてのジョブに適用されます:
name: CI Pipeline
permissions:
contents: read # すべてのジョブでデフォルトでリポジトリ内容は読み取り専用
issues: write # すべてのジョブで Issue に書き込み可能
jobs:
lint:
runs-on: ubuntu-latest
steps:
- run: echo "Lint with read-only access"
test:
runs-on: ubuntu-latest
steps:
- run: echo "Test with read-only access"
ジョブレベルの権限はワークフロー設定を上書きできます:
name: Release Pipeline
permissions:
contents: read # デフォルトは読み取り専用
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read # 読み取り専用を継承
steps:
- run: echo "Build needs read only"
release:
runs-on: ubuntu-latest
permissions:
contents: write # Release 作成のために書き込みに上書き
packages: write # Packages に公開
steps:
- name: Create Release
uses: actions/create-release@v1
実際のケース:あるプロジェクトの Release ワークフローで、build ジョブはコードを読むだけでよく、release ジョブは Release を作成し Docker イメージをプッシュする必要がありました。ジョブレベルの権限分離により、build ジョブが攻撃されても、リポジトリの内容に書き込むことはできません。
リポジトリレベルの権限モード
ワークフロー設定以外に、GitHub リポジトリ設定にも2つの権限モードがあります:
- Permissive(寛容):GITHUB_TOKEN はデフォルトですべての権限を読み書きできます。ワークフローで
permissionsが指定されていない場合に有効。 - Restricted(制限):GITHUB_TOKEN はデフォルトで contents と packages のみ読み取り可能。ワークフローは追加で宣言しないと書き込み権限を取得できない。
すべてのリポジトリを Restricted モードに設定することを推奨します。リポジトリの Settings > Actions > General で、「Workflow permissions」を見つけ、「Read repository contents and packages permissions」を選択します。
これで、ワークフローで permissions を書き忘れても、過剰な権限を持つことはありません。セキュリティ境界はリポジトリレベルで既にロックされています。
監査ログとコンプライアンスチェック
ワークフロー実行イベントの監査ログへの統合
2021 年 2 月から、GitHub は Actions ワークフロー実行イベントを組織監査ログに統合しました。これにより、誰がどのワークフローをトリガーしたか、どのような権限を使ったか、どの Secrets にアクセスしたかを追跡できます。
監査ログの入り口:組織の Settings > Security > Audit log。
主なフィールドには以下が含まれます:
action:イベントタイプ、例えばworkflow_run.create、workflow_run.completeactor:トリガーした主体、ユーザー名、App、またはgithub-actions[bot]の可能性repo:リポジトリパスtoken_scopes:使用された Token の権限範囲request_id:リクエスト追跡 ID、ログの関連付けに使用
実際の用途:異常なワークフロー実行を発見した場合、監査ログで追跡します。例えば、ある Secrets に異常アクセスがあった場合、workflow_run イベントを調べてトリガーした主体を見つけます。
エンタープライズ監査 API
組織が GitHub Enterprise を使用している場合、Audit Log API ですべての操作をクエリできます:
curl -H "Authorization: Bearer YOUR_TOKEN" \
"https://api.github.com/enterprises/YOUR_ENTERPRISE/audit-log?phrase=workflow_run"
返される JSON には詳細なイベントレコードが含まれます。SIEM システム(Splunk、Datadog など)にエクスポートして、継続的な監視が可能です。
コンプライアンス要件の概要
プロジェクトが SOC 2 や ISO 27001 コンプライアンスを満たす必要がある場合、CI/CD セキュリティは必須項目です。監査ログは最も直接的なコンプライアンス証拠です——CI/CD アクティビティを追跡し、異常を発見し、インシデントに対応する能力があることを証明します。
推奨設定:監査ログを外部システムにエクスポートし、重要なイベントのアラートルールを設定します。例えば:
- ワークフロー実行失敗率の急増
- Secrets への異常アクセス(短時間に大量読み取り)
- 未知の IP からのワークフロー実行
GitHub 2026 セキュリティロードマップの展望
GitHub は 2026 年 3 月に Actions セキュリティロードマップを公開し、6つの重要な新機能を計画しました。一部は既にリリースされ、一部はまだ開発中です。
ワークフローレベルの依存関係ロック
これは tj-actions タイプの攻撃に対する公式ソリューションです。package-lock.json のように、すべての Action 参照を SHA に固定します。ワークフローでは uses: tj-actions/changed-files@v45 と書いたままでも、ロックファイルが対応する SHA を記録します。メンテナが Action を更新しても、ロックファイルは自動的に変わりません——あなたが能動的に審査し更新する必要があります。
この機能はまだリリースされていませんが、今から手動で SHA 固定を行うことができます。
Layer 7 ネイティブアウトバウンドファイアウォール
CI/CD フローの外部ネットワークアクセスをネイティブに制御します。例えば、ワークフローはあなたの AWS API にのみアクセスでき、任意の外部ネットワークにはアクセスできないように制限します。
現在、これを実現するには、自己ホストランナー + カスタムネットワークポリシーが必要です。2026 年にリリースされると、GitHub クラウドランナーでもアウトバウンドファイアウォールを設定できるようになります。
Scoped Secrets
より細かい Secrets スコープ制御。例えば、ある Secrets は特定のブランチまたは特定のジョブでのみアクセス可能にします。現在、Secrets のスコープはリポジトリレベルまたは環境レベルで、粒度が十分ではありません。
ポリシー駆動実行制御
信頼境界、承認、証明ゲートを定義します。例えば、フォークからの PR はすべて、人間の審査を経ないとワークフローをトリガーできないように要求します。または、ワークフローはセキュリティスキャンに合格しないと実行できないように要求します。
これは環境保護ルールに似ていますが、より細かく、ロジックがより複雑です。
Actions Data Stream
CI/CD アクティビティのリアルタイム可視性。監査ログに似ていますが、リアルタイムでプッシュされ、事後クエリではありません。SIEM システムに接続してリアルタイム監視が可能です。
OIDC カスタム属性クレーム
クラウドプロバイダー認証の強化。OIDC Token により多くのカスタム属性を含めることができます。例えば、リポジトリタグ、ブランチ情報など。Vault や AWS はこれらの属性に基づいてより細かい認可判断が可能になります。
結論
tj-actions インシデントから現在まで、私が学んだ核心的な教訓は3点です:
第一に、SHA 固定。 タグ参照ではなく、サードパーティ Action は完全な SHA で参照します。これは 23,000 のリポジトリが教訓として得た経験です。
第二に、最小権限。 GITHUB_TOKEN のデフォルト権限は大きすぎるため、permissions キーで制限し、リポジトリを Restricted モードに設定します。
第三に、監査ログ。 Actions イベントは既に監査に統合されているため、定期的に異常な実行をチェックし、監視システムにエクスポートします。
これら3点は、今すぐ設定できます。GitHub 2026 新機能のリリースを待つ必要も、新しいツールに乗り換える必要もありません。リポジトリを開き、Action 参照、権限設定、監査ログを確認してください。30分で基本的な防護が完了します。
結局のところ、CI/CD セキュリティは高度な技術ではなく、細部への習慣です。SHA 固定を一つ増やすごとに、過剰な権限を一つ減らすごとに、リスクは小さくなります。tj-actions インシデントは教えてくれました:攻撃者は高度な手段を必要とせず、私たちが一つの小さな細部を見落とすだけでよいのです。
GitHub Actions セキュリティ保護設定フロー
SHA 固定から権限制御まで、CI/CD セキュリティ強化の3つの主要ステップ。
⏱️ 目安時間: 30 分
- 1
ステップ1: SHA で Action 参照を固定
すべてのワークフローファイルを確認し、`uses: action@tag` を `uses: action@full-sha` に変更し、タグ改ざんを回避。`pinact` などのツールで既存の参照を一括変換。 - 2
ステップ2: permissions キーを設定
ワークフローまたはジョブレベルで permissions キーを追加し、必要な権限のみを付与。例:読み取り専用操作には `permissions: contents: read`、Release 作成には `permissions: contents: write`。リポジトリを Restricted モードに設定して最終防衛線に。 - 3
ステップ3: 監査ログ監視を有効化
組織の Settings > Security > Audit log で Actions ワークフロー実行イベントを確認。主要イベントのアラートを設定:ワークフロー実行失敗率の急増、Secrets への異常アクセス、未知の IP からのワークフロー実行。ログを SIEM システムにエクスポートして継続監視。
FAQ
なぜ SHA 固定でなくタグ参照を使うべきなのか?
GITHUB_TOKEN の権限はどう制御する?
ワークフローが攻撃されたかどうかを検知するには?
OIDC + Vault は組み込み Secrets より何が優れているのか?
サードパーティ Action は使える?
6 min read · 公開日: 2026年5月16日 · 更新日: 2026年5月17日
GitHub Actions 完全ガイド
検索からこのページに来た場合は、前後の記事もあわせて読むと同じテーマの理解がかなり早く深まります。
関連記事
GitHub Actions Matrix ビルド:マルチバージョン並列テストの実践
GitHub Actions Matrix ビルド:マルチバージョン並列テストの実践
GitHub Actions 入門:YAML ワークフローの基礎とトリガー設定
GitHub Actions 入門:YAML ワークフローの基礎とトリガー設定
GitHub Actions 入門:YAML ワークフローの基礎とトリガー設定
コメント
GitHubアカウントでログインしてコメントできます