テック技報最新号へ →
セキュリティ

Issue や PR のタイトルに仕込まれた $() で CI の環境変数を抜く攻撃:通知・ラベル付けワークフローを点検する

外部の人が書ける Issue・PR・コメントの文字列を run: に ${{ }} で直接埋め込むと、二重引用符の中でも $() が実行される。2026-09-01 に 61 リポジトリへ 83 件の仕込み投稿が送られた実例があり、値は env: 経由で渡すのが基本。

@MalwareBibleJP · 2026-11-03
#github-actions#ci#injection#secrets#supply-chain

実際に起きたこと(Radware の調査)

  • 2026-09-01 の約 43 分で 77 件、6 時間余り後に 6 件の投稿。仕込み先はタイトル 31 件、本文 17 件、コメント 35 件。agno、ToolJet、trivy、mlflow、Podman などが標的になった。
  • 中身は「環境変数を Base64 にして攻撃者のサーバーへ HTTP 送信する」コマンド置換。承認やマージは要らず、投稿されるだけでワークフローが動く。
  • 実行が確認されたのは、PR タイトルの形式チェック(PX4-Autopilot)と、Issue を Discord に通知する処理(workout-cool)の 2 件。どちらも「ビルドではない小さな補助ワークフロー」だった。
  • 投稿元の一部は開発歴のある既存アカウントで、乗っ取られた可能性がある。投稿者の見た目では判断できない。
  • 別のプロジェクトでは、認証情報にアクセスできる AI のコメントレビューエージェントが投稿を読みうる状態だった。

直し方

悪い例(タイトルがそのままシェルスクリプトになる):

yaml
- run: echo "New issue: ${{ github.event.issue.title }}"

良い例(値は環境変数として渡し、シェル側で文字列として扱う):

yaml
- env:
    TITLE: ${{ github.event.issue.title }}
  run: printf 'New issue: %s\n' "$TITLE"
  • 読み出した値を eval や bash -c に渡したり、別のコマンド文字列に組み込んだりしない。
  • permissions: を最小にし、外部投稿で起動するワークフロー(issues、issue_comment、pull_request_target など)と、シークレットを使うジョブを分ける。
  • Discord・Slack の Webhook URL もシークレット。通知ワークフローで流出すると、なりすまし投稿に使われる。

点検のしかた

  1. .github/workflows/ で ${{ github.event. を含む run: 行を探す(例: rg -n 'run:.*\$\{\{ *github\.event' .github/workflows と、複数行の run: | の中も確認)。title、body、head_ref、comment.body が要注意。
  2. 静的解析ツール(zizmor、actionlint など)でテンプレートインジェクションを検出する。
  3. 実行痕跡(Bash の警告、想定外の外部通信)があれば、そのジョブから見えたシークレットを失効・再発行し、監査ログを確認する。
  4. AI レビューボットなど、外部投稿を読んで動く仕組みが持つトークンの権限も見直す。

関連

出典

X の元の投稿を開く