はじめに
本稿では、ソフトウェア開発の自動化に不可欠なツールとなりつつあるGitHub Actionsに潜む、深刻なセキュリティリスク「ワークフローインジェクション」について解説します。この脆弱性は、GitHubリポジトリで見つかる最も一般的な脆弱性の一つとされていますが、その仕組みを理解し、適切な対策を講じることで防ぐことが可能です。
本稿は、GitHubの公式ブログに掲載された記事「How to catch GitHub Actions workflow injections before attackers do」を基に、攻撃の具体的な手法から、開発者が今すぐ実践できる予防策、そしてGitHubが提供する強力な静的解析ツール「CodeQL」の活用法まで、分かりやすくお伝えします。
参考記事
- タイトル: How to catch GitHub Actions workflow injections before attackers do
- 発行元: The GitHub Blog
- 発行日: 2025年7月16日
- URL: https://github.blog/security/vulnerability-research/how-to-catch-github-actions-workflow-injections-before-attackers-do/

要点
- GitHub Actionsにおけるワークフローインジェクションは、CI/CDプロセスにおける一般的な脆弱性の一つである。
- この攻撃は、issueのタイトルやブランチ名など、信頼できない外部からの入力をワークフローが直接実行してしまうことで発生する。
- 主な原因は、ワークフローファイル内のrunステップなどで、${{ }}構文を用いて信頼できないデータを直接展開することにある。
- 対策として、信頼できない入力は一度環境変数に格納してから使用すること、ワークフローの権限を必要最小限にする最小権限の原則を遵守すること、そして特に権限の強いpull_request_targetトリガーの使用を慎重に検討することが挙げられる。
- GitHubの静的コード解析ツール「CodeQL」は、汚染追跡(Taint Tracking)機能により、この種の脆弱性を自動で検出するのに非常に有効である。
詳細解説
GitHub Actionsとワークフローインジェクションの基本
まず前提として、GitHub Actionsは、ソフトウェアのビルド、テスト、デプロイといった一連の作業を自動化するCI/CD(継続的インテグレーション/継続的デリバリー)の仕組みです。この自動化のプロセスは「ワークフロー」と呼ばれ、リポジトリ内のYAMLファイルで定義されます。
ワークフローインジェクションとは、この自動化プロセスに悪意のある第三者が介入する攻撃手法です。具体的には、攻撃者がissueのタイトルやブランチ名、プルリクエストのコメントといった、ワークフローが参照する可能性のある入力データに、実行可能なコマンドを埋め込みます。ワークフローがこの信頼できない入力を無防備に扱うように設定されていると、埋め込まれたコマンドが意図せず実行されてしまうのです。
例えば、以下のようなワークフローを考えてみましょう。これは、新しいissueが作成された際に、そのタイトルをログに出力する簡単なワークフローです。
- name: print title
run: echo "${{ github.event.issue.title }}"
一見無害に見えますが、ここに脆弱性が潜んでいます。もし攻撃者が、タイトルを「hello; touch pwned.txt」のような文字列にしてissueを作成した場合、${{ github.event.issue.title }}の部分がそのまま展開され、シェルは以下のように解釈します。
echo "hello; touch pwned.txt"
これにより、echoコマンドに続いてtouch pwned.txtというコマンドが実行され、リポジトリの実行環境内に意図しないファイルが作成されてしまいます。これが、情報を盗み出したり、システムを破壊したりする、より悪質なコマンドであれば、被害は甚大なものになり得ます。
脆弱性を生まないための4つの予防策
幸いなことに、この脆弱性はいくつかの基本的な対策を講じることで防ぐことができます。
対策1:環境変数を利用する
最も簡単で効果的な対策は、信頼できない入力をrunステップで直接展開しないことです。代わりに、一度環境変数に格納してから、その変数を参照するようにします。
- name: print title
env:
TITLE: ${{ github.event.issue.title }}
run: echo "$TITLE"
このように変更することで、TITLEという環境変数にissueのタイトルが文字列として代入されます。runステップで$TITLEを参照する際には、中身がコマンドとして解釈されるのを防ぐことができ、インジェクションのリスクを大幅に低減できます。
対策2:最小権限の原則を徹底する
ワークフローが実行される際、それはGITHUB_TOKENという特殊なトークンに紐づいた権限を持っています。万が一インジェクション攻撃が成功した場合、被害の大きさはこの権限の強さに依存します。
したがって、ワークフローには業務を遂行するために必要な最小限の権限のみを与えるべきです。これは「最小権限の原則」として知られるセキュリティの基本です。
permissions:
contents: read
issues: write
上記のように、ワークフロー全体またはジョブ単位でパーミッションを明示的に設定し、不要な書き込み権限などを無効にしておくことで、被害を最小限に抑えることができます。
対策3:「pull_request_target」の危険性を理解する
ワークフローをトリガーするイベントとして、pull_requestとpull_request_targetがあります。この二つは似ていますが、セキュリティの観点では大きな違いがあります。
- pull_request: フォークされたリポジトリから送られたプルリクエストの場合、セキュリティ上の理由から、書き込み権限が制限され、シークレット(APIキーなど)にもアクセスできません。
- pull_request_target: こちらは、プルリクエストが送られたターゲット側のリポジトリのコンテキストで実行されます。そのため、書き込み権限やシークレットへのアクセスが可能です。
この性質上、pull_request_targetは非常に強力であると同時に危険も伴います。もしpull_request_targetを使ったワークフローでインジェクションが発生すると、攻撃者にリポジトリを書き換えられたり、シークレットを盗まれたりする可能性があります。
ラベル付けなど、どうしても必要な場合を除き、安易にpull_request_targetを使用するのは避け、pull_requestを使用するのが安全です。
対策4:不要なブランチをクリーンに保つ
開発の過程で作成された古いフィーチャーブランチなどが放置されていると、それらも攻撃の対象となり得ます。特にpull_request_targetトリガーを使用している場合、攻撃者はアクティブでないブランチに対してプルリクエストを送り、脆弱なワークフローを起動させることができます。
マージ済み、あるいは不要になったブランチは定期的に削除する習慣をつけ、攻撃対象領域を減らすことが重要です。
CodeQLによる自動検出
これまで述べた対策を手動で実施するのに加え、GitHubは「CodeQL」という非常に強力な静的コード解析ツールを提供しています。CodeQLは、コードをデータベース化し、クエリを実行して脆弱なパターンを検出します。
特にワークフローインジェクションに対しては、「汚染追跡(Taint Tracking)」という機能が有効です。これは、信頼できない外部入力(ソース)が、危険な処理(シンク、例えばコマンド実行)に到達するまでのデータの流れを追跡し、リスクを警告してくれるものです。
CodeQLによるコードスキャンは、リポジトリの「Settings」→「Code security and analysis」から簡単に有効にできます。デフォルト設定を選択するだけで、GitHub Actionsのワークフローファイルも自動的にスキャン対象に含まれます。
CodeQLは100%完璧なツールではありませんが、人間が見落としがちな脆弱性を発見する上で、極めて信頼性の高い防御策となります。
まとめ
本稿では、GitHub Actionsにおけるワークフローインジェクションの脅威とその対策について詳しく解説しました。この脆弱性は広く存在しますが、その仕組みは決して複雑ではありません。
- 信頼できない入力は、環境変数を経由して安全に扱う。
- ワークフローには最小限の権限しか与えない。
- pull_request_targetの使用は慎重に判断する。
- CodeQLのような自動化ツールを活用して、脆弱性を早期に発見する。
これらの基本的な対策を日々の開発プロセスに組み込むことで、CI/CDパイプラインの安全性を高め、悪意のある攻撃から貴重なコード資産を守ることができます。セキュリティは一度設定して終わりではなく、継続的な意識と改善が不可欠です。