Snowflakeの公開リポジトリにおいて、GitHub Actionsワークフローの不備を突いて社内Jiraの認証情報が取得できる状態となっていたことがセキュリティ企業Wizの調査で判明しました。当初はGitHub Copilotの自動修正コードが原因と騒がれましたが、実際には古典的なシェルインジェクションと人間・自動チェック双方の見落としが重なった複合要因でした。AIがCI/CDやコードレビューに関与する時代において、開発パイプラインのセキュリティ設計と責任モデルをどう見直すべきかを解説します。
何が起きたのか:SnowflakeのCI/CDで実証されたJira認証情報の露出
2026年6月、クラウドデータ基盤を提供するSnowflakeの公開リポジトリ「snowflakedb/snowflake-connector-net」において、社内Jiraの認証情報が露出する脆弱性が存在することが判明しました[1]。この脆弱性は、セキュリティ企業Wizの調査チームによって発見されました[1]。対象となったのは、Pull Request(PR)#1218で変更されたワークフローファイル「.github/workflows/jira_issue.yml」です[1]。
脆弱なコードパターンは、2025年8月25日のコミット「094038e」でリポジトリに追加されていました[2]。その後、2026年6月18日にPR #1218がmasterブランチへマージされたことで、この処理が有効になりました[2]。
Wizの研究チームは、自社が開発する自律型セキュリティエージェント「Wiz Red Agent」を使用しました[1]。細工したIssueタイトルを送信することでシェル構文を脱出させ、GitHub Actionsの実行環境(ランナー)上で任意のコマンドを実行させることに成功しました[6]。その結果、ワークフロー内に保持されていたJira APIトークンなどの認証情報が、外部の通信(out-of-band callback)経由でWiz側へ送信されました[6]。
取得されたAPIトークンは「qa@snowflake.net」というアカウントで認証されました[7]。このアカウントは「snowflakecomputing.atlassian.net」において、エンジニアリング、セキュリティコンプライアンス、バグ報奨金追跡の各プロジェクトに対する閲覧権限を保持していました[7]。
本件はSnowflakeのHackerOne脆弱性開示プログラムを通じて報告された研究者による概念実証(PoC)です[11]。Snowflakeの監査ログ調査によると、脆弱性が有効だった約5日間の露出期間中にアクセスした主体はWizのみでした[11, 40]。未知の第三者による不正アクセスを示す証拠は確認されていません[11, 20]。また、公開資料において確認されたアクセス対象は社内Jiraのプロジェクト情報であり、顧客データやSnowflake本体のデータプレーンへのアクセスは報告されていません[46]。
後輩「Jiraが侵害された」と聞いて驚きましたが、外部の攻撃者に情報が盗まれたわけではないんですね?
先輩はい。これはWizが脆弱性開示プログラムの中で実施した技術検証です。
後輩なるほど。監査ログでもアクセスしたのはWizだけだと確認されているわけですね。
先輩その通りです。未知の第三者による不正アクセスは確認されていません。
脆弱性の技術的メカニズム:信頼できないIssue入力と条件分岐の不備
今回の脆弱性は、AI特有の新しい欠陥ではありません。GitHub Actionsにおける「ワークフロースクリプトインジェクション」と呼ばれる古典的な実装ミスが根本的な原因です[36, 47]。対象のワークフロー「jira_issue.yml」は、リポジトリに新しいIssueが作成された際(issues: opened)に自動起動する設定となっていました[4, 12]。
このジョブでは、作成されたIssueのタイトルと本文を取得し、JiraのREST APIへチケットを起票する処理を行っていました[5, 15]。ジョブ内には「JIRA_BASE_URL」「JIRA_USER_EMAIL」「JIRA_API_TOKEN」が渡されており、curlコマンドによるBasic認証で通信していました[5, 15]。
問題となったのは、Issueのタイトルをシェルスクリプト(runブロック)内で処理する記述方法です。変更前のコードでは環境変数とjq --argを用いて安全にJSONデータを構築していました[3]。しかし、問題のコミットではこれらが削除され、以下のようにrunブロック内の単一引用符文字列へ直接展開する形式へ書き換えられました[3, 24]。
TITLE=$(echo '${{ github.event.issue.title }}' | sed 's/"/\\"/g' | sed "s/'/\\\'/g")
GitHub Actionsでは、シェルスクリプトが実行される前に${{ }}で囲まれた式が展開されます[14, 30]。そのため、タイトルに単一引用符が含まれていると、スクリプトの実行前にecho '...'の枠組みから脱出してしまいます[3, 14]。後段に記述されたsedによるエスケープ処理は、テンプレート展開の後に実行されるため、インジェクションを防ぐことができませんでした[3, 14]。
さらに、実行を制限するはずの条件分岐にも不備がありました。ワークフローには以下の条件式が設定されていました[4, 13]。
if: ((github.event_name == 'issue_comment' ... ) || (github.event_name == 'issues' && github.event.pull_request.user.login != 'whitesource-for-github-com[bot]'))
Issue作成イベント(issues)では、Pull Requestのコンテキスト(github.event.pull_request)は常にnullとなります[4, 13]。その結果、「null != 'whitesource-for-github-com[bot]'」という比較になり、評価結果は常に真(true)となってしまいます[4, 13]。これにより、任意のGitHubユーザーが誰でもこの実行経路を起動できる状態になっていました[4, 13]。
ジョブ自体が持つGitHubの権限は「issues: write」に制限されていましたが、ジョブ内にJiraのAPIトークンが渡されていたため、コマンド実行によってJiraの認証情報が窃取される構造となっていました[15, 16]。
後輩sedでエスケープしているのに、なぜシェルインジェクションが起きたんですか?
先輩GitHub Actionsの式展開が、シェルスクリプトの実行よりも先に行われるからです。
後輩あらかじめ文字列として埋め込まれてしまうから、その後のsedでは手遅れなんですね。
先輩そうです。単一引用符で囲んでいても、展開時に閉じられてしまえば防げません。
誤解の解明:Copilotの関与実態と自動セキュリティスキャンの限界
本件が最初に報じられた際、「GitHub Copilotが生成した自動修正コードが脆弱性を生み出した」という文脈で語られる場面が見られました。しかし、公開されている一次情報の履歴を精査すると、事実関係は異なります[8, 21]。
PR #1218は合計4つのコミットで構成されていました[22, 25]。このPRにおいて「Copilot Autofix」が明示的に関与したと記録されているのは、別のワークフロー「jira_close.yml」を修正したコミット「6d0e2fa」のみです[8, 18, 23]。このコミットは秘密情報を環境変数へ移す修正を行っており、脆弱性が存在した「jira_issue.yml」には触れていません[23, 29]。
一方、脆弱性を導入したコミット「094038e」は、人間の開発者「sfc-gh-hpathak」によるコミットとして記録されています[24, 29, 34]。Wiz自身も後の更新において、この脆弱な変更自体がAI支援によって作成されたものかどうかは不明であるとしています[8, 21, 37]。したがって、Copilotが脆弱なコードを生成したと断定することはできません[21, 55]。
むしろ注目すべき点は、人間と自動ツールの双方がこの脆弱性を見逃してしまったプロセスにあります[28, 43]。PR #1218はGitHub Advanced SecurityによるAIレビューや各種チェックを受け、87件中83件のチェックが合格した状態で、人間のレビュアーによって承認・マージされました[9, 25, 27]。Wizの報告によると、GitHub Advanced Securityのスキャンは脆弱なワークフローファイルを解析対象として抽出していたものの、重大なインジェクション脆弱性として検出しませんでした[9, 26, 39]。GitHubの公式ドキュメントによると、CodeQLにはGitHub Actionsのコードインジェクションを検出するクエリが存在します[51]。ただし、この事例では検出に至りませんでした[9, 56]。
また、CopilotによるPull Requestレビューは「Comment(コメント)」として扱われ、マージを阻止する「Request changes」や必須承認のステータスにはなりません[39, 49]。GitHub自身も、Copilot AutofixなどのAIセキュリティ機能は非決定的な生成モデルに基づいており、脆弱性の修正に失敗したり新たな脆弱性を導入したりする可能性があるため、人間による入念な確認が必要であると説明しています[38, 50, 57]。
本件の正確な構図は、「AIが脆弱性を作った」ということではありません。「人間の変更によって安全な実装が置き換えられ、自動スキャンと人間のレビューをすり抜けてmasterブランチにマージされた」という複合的な見落としです[28, 43]。
後輩「AIが危ないコードを書いた」という話ではなかったんですね。
先輩はい。履歴上、脆弱なファイルを作ったのは人間のコミットです。AI支援があったかは不明とされています。
後輩なのに自動チェックも人間のレビューも通ってマージされてしまった、と。
先輩そこが本質です。自動化ツールが存在していても、見落としが起きる可能性は残ります。
なぜ今問題となるのか:自律型AIエージェントの普及と攻撃サイクルの高速化
本件において技術的な新しさを示したのは、脆弱性の種類そのものではなく、脆弱性を検証・実証した「攻撃側の手法」です[42, 44]。Wizの研究チームが使用した「Wiz Red Agent」は、指示に基づいて自律的に調査や攻撃検証を行うAIエージェントです[1]。Red Agentが最初に送信した攻撃ペイロードは、シェルの閉じ括弧の扱いに起因してbashの構文エラーとなりました[17, 42]。
通常のスクリプトであればここで停止しますが、Red Agentは実行エラーの内容を自律的に解析しました[17, 42]。そして、シェルブロックを正しく閉じるようにペイロードを「; echo '」の形へと即座に修正し、再実行によって認証情報の取得に成功しました[17, 42]。
一方で、開発側においてもAIエージェントの活用が急速に進んでいます。GitHub Copilotの機能は、単なるコード補完から大きく進化しています。開発者がコードスキャンのアラートを割り当てると、関連ファイルを自律的に調査し、修正コードを生成してテストや検証を反復した上でPull Requestを作成する「Agentic Autofix」などが提供されています[31, 48]。また、バックグラウンドでテストを通過させてマージまで進める機能も登場しています[32]。
このように、開発側でAIがコード生成やレビューに関与する一方で、セキュリティ検証や攻撃側でもAIエージェントが構文エラーを自己修正しながら高速に検証を進める時代に入りつつあります[44]。人間の介在が薄れる中で見落としが発生すると、極めて短時間で弱点を突かれるリスクが高まります[44]。
誰に影響するのか:CI/CDで外部イベントと高権限トークンを扱う組織
本件はSnowflake特有の問題や、Copilotを利用している組織だけに限定された話ではありません[45]。以下のような条件が重なるCI/CD環境では、同様のリスクが生じ得ます[36, 45]。
- 外部入力を直接展開している: Issueのタイトルや本文、PRのコメントなど、外部ユーザーが自由に制御できる文字列を、スクリプト(runブロック)内に
${{ }}で直接埋め込んでいる。 - トリガー条件の制限が不十分:
issuesやissue_commentなど、外部から起動できるイベントにおいて、実行権限のチェックが正しく機能していない。 - 過剰な権限やシークレットの付与: 外部入力を処理する同一のジョブ内に、内部SaaS(Jira等)やクラウドリソースへのアクセス権を持つ認証情報が渡されている。
開発の利便性を高めるために「Issueが作成されたら自動で社内チケットを作る」といった連携を組むケースは珍しくありません。しかし、入力値の処理とシークレットの管理に隙があれば、リポジトリが社内ネットワークへの侵入の足がかりになり得ます[5, 15]。
後輩うちの開発リポジトリでも、Issueから自動でチケットを作るワークフローを動かしていますよね。
先輩まさにその構成ですね。外部からの文字列をどうスクリプトに渡しているか確認が必要です。
後輩シェルの中で直接式展開していたら、同じ問題が起こり得るわけですね。
先輩はい。外部入力を直接展開せず、環境変数を経由させることが基本原則になります。
何を変えるべきか:Snowflakeの迅速な修正と企業がとるべき多層防御策
Snowflakeは開示を受けた当日である2026年6月23日に、PR #1402(コミット「1dc7766」)をmasterブランチへマージして修正を完了しました[10, 19]。また、露出したJira APIトークンは直ちに無効化(revoke)され、ローテーションされました[10, 19]。Snowflakeが実施した修正内容は、GitHubが推奨する安全な実装パターンに立ち返るものでした[10, 33, 35]。
# 修正後の実装パターン (安全な例)
- name: Create Jira Issue
env:
ISSUE_TITLE: ${{ github.event.issue.title }}
ISSUE_BODY: ${{ github.event.issue.body }}
run: |
PAYLOAD=$(jq -n \
--arg title "$ISSUE_TITLE" \
--arg body "$ISSUE_BODY" \
'{fields: {summary: $title, description: $body, ...}}')
この実装では、Issueのタイトルや本文をまず中間環境変数(env)として受け取ります[10, 35]。その上で、jqコマンドの--argオプションを使用してJSON文字列を組み立てています[10, 41]。環境変数を経由させることで、値はデータとして安全に扱われ、シェルの構文として解釈される危険を排除できます[33, 53]。
CI/CDパイプラインの安全性を確保するために、企業は以下の多層防御策を講じる必要があります。
- 外部入力の直接展開の禁止: 信頼できないコンテキスト値をrunブロック内に直接記述せず、必ず環境変数を経由させます[33, 53]。
- 最小権限の原則とシークレットの分離: 外部イベントで動作するワークフローには、不要なシークレットを渡さない設計にします[53]。
- CODEOWNERSによる重要ファイルの保護:
.github/workflowsディレクトリや認証関連のコードについて、担当者と必須レビューを定義する「CODEOWNERS」を設定し、権限を持つ人間による差分確認と承認を必須とします[52]。 - 自動チェックの多層化: 静的解析ツールやAIレビューの結果を過信せず、シークレットスキャンやワークフロー専用の構文チェックなど、複数の検証レイヤーを組み合わせます[54]。
AI支援ツールの導入が進む現代だからこそ、CI/CDパイプラインの基本原則である入力値の検証と最小権限の徹底が重要になります[43, 53]。


