0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Cursorのallowlist、gitを許可したのに任意コード実行になる仕組みを再現した

0
Posted at

TL;DR

  • Cursor の CVE-2026-22708 は、コマンド allowlist を有効にした Auto-Run Mode でも、シェルの組み込みコマンド(export など)が allowlist を通らずに実行できてしまう脆弱性です(GitHub Security Advisory GHSA-82wg-qcm4-fp2w)。
  • 攻撃の肝は「許可したコマンドそのものを差し替える」ことではなく、環境変数を汚染して、許可済みコマンドの実行結果を横取りする ことです。
  • 筆者は Cursor 本体ではなく、その土台であるシェルの挙動を手元の bash で再現しました。git だけを許可した状態でも、PATH を書き換えるだけで偽の git が動き、GIT_EXTERNAL_DIFF を書き換えるだけで正規の git diff から任意コマンドが実行されました。
  • 対策は Cursor 2.3 への更新が第一ですが、「allowlist に入れたコマンドは安全」という前提そのものが崩れている点が本質です。

対象読者は、AI コーディングエージェント(Cursor / Claude Code / Codex など)を自動実行モードで動かしている、あるいは CI・開発環境に組み込もうとしている開発者です。

はじめに

コマンド allowlist は、AI エージェントに「この一覧のコマンドだけは確認なしで実行してよい」と許可する仕組みです。git statusls のような無害なコマンドを並べておけば、いちいち承認を押さずに作業が進みます。

ここで一つ疑問が浮かびます。allowlist に git を入れたとして、本当に「安全な git」だけが動くのでしょうか。実行されるバイナリがすり替わっていたら、あるいは git の挙動を変える環境変数が仕込まれていたら、allowlist は何を守っているのでしょうか。

Cursor の CVE-2026-22708 は、まさにこの隙を突いたものでした。本記事では、脆弱性の構造を確認したうえで、その中核にあるシェルの挙動を手元で再現します。Cursor 固有の内部実装には踏み込めないため、そこは公式アドバイザリの記述に依拠しますが、「許可済みコマンドが乗っ取られる」というメカニズム自体は、どの環境でも成立する普遍的な問題 であることを示します。

CVE-2026-22708 の概要(公式アドバイザリより)

まず二次情報として、公式アドバイザリが述べている事実を整理します。

  • 影響範囲: Cursor 2.2 以前。修正版は 2.3GHSA-82wg-qcm4-fp2w)。
  • 前提条件: エージェントが Auto-Run Mode(既定ではない)で動作し、かつ Allowlist モードが有効であること。
  • 仕組み: 公式アドバイザリは「trusted command に影響する環境変数を設定・変更・削除してシェル環境を汚染する」と説明しています。シェルの組み込みコマンドが allowlist チェックを丸ごと素通りするため、PATH のような環境変数を書き換えて、許可済みコマンドの実行方法を乗っ取れます。
  • 想定被害: prompt injection と連鎖すると、許可した覚えのないコマンドが動く、すなわち任意コード実行につながり得ます。

修正内容としてアドバイザリが挙げているのは「エッジケースまわりのコマンドパースの改善」です。つまり、組み込みコマンドや環境変数の扱いをパーサーが取りこぼしていた、という素直な話です。

ここまでが公式の記述です。以下は筆者が手元の bash で実際に確かめた内容に切り替わります。

手元で再現する:allowlist は「バイナリ名」しか見ていない

再現に使ったのは、Cursor ではなく素の bash です。allowlist の本質は「起動しようとしているコマンド名が一覧にあるか」を見る点にあり、その名前で実際にどのバイナリが起動されるかは、シェルの PATH 解決に委ねられています。この分離が攻撃面になります。

ベクター1:PATH を汚染して偽の git を起動させる

git だけを許可した状況を想定し、攻撃者が仕込む偽 git を用意して、export PATH=... で先頭に差し込みました。

# 攻撃者が仕込む偽 git(任意コードを実行する)
mkdir -p "$WORK/evil"
cat > "$WORK/evil/git" <<'EOF'
#!/usr/bin/env bash
echo "[!!! 任意コード実行 !!!] 偽 git が動作: whoami=$(whoami)" >&2
exit 0
EOF
chmod +x "$WORK/evil/git"

# シェル組み込みの export で PATH を汚染(allowlist を経由しない)
export PATH="$WORK/evil:$PATH"

# allowlist 済みの「安全な」git を呼ぶ
git status

実行結果は次のとおりでした。

=== ステップ2: allowlist 済みの「安全な」git を呼ぶ ===
[!!! 任意コード実行 !!!] 偽 git が動作: whoami=root

=== 結論: allowlist は git を許可しただけなのに、実行されたのは偽 git ===

git status を呼んだつもりが、動いたのは攻撃者の偽 git でした。allowlist は「git という名前」を許可しただけで、その名前がどのファイルに解決されるかは一切見ていません。PATH を先頭一つ書き換えるだけで、承認ゼロのまま任意コードが走ります。

筆者が改めて怖いと感じたのは、PATH の書き換えが export というごくありふれた組み込みコマンド一つで済む点です。ファイルを一つも作らずに alias git='...' を仕込む変種も同じ理屈で成立します。「新しいコマンドの実行」に見えないので、コマンド単位の allowlist では原理的に検知できません。

ベクター2:バイナリはそのまま、環境変数だけで乗っ取る

ベクター1 は「偽バイナリを PATH で優先させる」手口でした。より発覚しにくいのは、正規の git バイナリをそのまま使いながら、git が参照する環境変数だけを汚染する 手口です。git には外部プログラムを呼び出すための環境変数がいくつもあり、その一つ GIT_EXTERNAL_DIFF を使いました。

# payload.sh は任意コードを実行するスクリプト
export GIT_EXTERNAL_DIFF="$WORK/payload.sh"

# allowlist 済みの git diff を非対話で呼ぶ
git -C "$WORK" diff

結果は次のとおりです。

--- allowlist 済みの git diff を非対話で呼ぶ(パイプ済み) ---
[!!! payload !!!] GIT_EXTERNAL_DIFF 経由で任意コード実行: user=root

差分を表示するだけの git diff から、攻撃者のスクリプトが実行されました。ここで動いている git は正真正銘の /usr/bin/git です。バイナリの完全性を検証しても、PATH を監視しても、このベクターは素通りします。書き換わったのは環境変数一つだけだからです。

なお、同じく外部プロセスを呼ぶ GIT_PAGER も試しましたが、こちらは出力がパイプ(非対話)だと git がページャ自体を無効化するため、今回の非対話実行では発火しませんでした。すべての環境変数が同じ条件で使えるわけではない、という点は正直に補足しておきます。攻撃者にとっては、対話・非対話それぞれで確実に発火するベクターを選べばよいだけの話です。

なぜ「コマンド単位の許可」では守れないのか

2 つのベクターに共通するのは、allowlist が検査するレイヤーと、実際にコードが実行されるレイヤーがずれている ことです。

allowlist は「コマンド名」という表層を見ています。しかし実際に何が起動するかは、その下にあるシェルの名前解決(PATH)と、コマンドが読む環境変数(GIT_* など)が決めます。組み込みコマンドがこのチェックを素通りする限り、攻撃者は「許可された名前」を保ったまま、中身だけを差し替えられます。

CVE-2026-22708 が Cursor 固有の実装バグに見えて、実はもっと根が深いのはこのためです。Cursor 2.3 の修正は「パーサーが取りこぼしていた組み込みコマンドを承認対象に含める」という方向で、これは正しい対処です。ただし、環境変数を汚染する経路は export だけではありません。防御側は「特定のコマンドを塞ぐ」発想から、「信頼できない入力を読んだ後は環境そのものを信用しない」発想へ切り替える必要があります。

対策:バージョン更新の先にあるもの

第一の対策は明快です。Cursor を 2.3 以降に更新する こと。これで組み込みコマンドが承認対象に入り、export PATH=... のような一手は塞がれます。

そのうえで、allowlist に頼る運用者が持っておくべき視点を挙げます。

  • Auto-Run Mode を常用しない。公式アドバイザリも前提条件に挙げているとおり、既定ではないこのモードを漫然と有効にしないことが、そもそもの露出を減らします。
  • 信頼できない入力を読むエージェントは環境を隔離する。prompt injection の起点は外部から読み込んだテキストです。ネットワークやファイルから untrusted な内容を読ませるセッションは、コマンド実行を許さない、あるいは使い捨て環境で走らせるのが安全です。この方向は各エージェントが機能として持ち始めており、たとえば Claude Code は v2.1.248 で、組み込みのコマンド実行ツールと WebFetch を外してファイルツールを作業ディレクトリに限定する --restricted フラグを追加しています(Claude Code changelog)。
  • allowlist を「安全の証明」と読み替えない。allowlist が保証するのは「一覧にある名前が呼ばれた」ことだけで、「安全なコードが動いた」ことではありません。本記事の 2 つの再現は、その差を具体的に示しています。

まとめ

CVE-2026-22708 は、Cursor 2.2 以前の Auto-Run Mode + Allowlist という条件下で、シェルの組み込みコマンドが allowlist を素通りする脆弱性でした。筆者が手元の bash で再現したところ、exportPATH を汚染すれば偽の git が承認なしで動き、GIT_EXTERNAL_DIFF を書き換えれば正規の git diff から任意コマンドが実行されました。どちらも「新しいコマンドの実行」には見えないため、コマンド単位の allowlist では検知できません。

修正版 2.3 への更新は必須ですが、それ以上に「許可したコマンド名」と「実際に動くコード」は別物である、という前提を運用に組み込むことが重要です。allowlist は入口の名札を確認しているだけで、中で何が起きるかまでは見ていないのです。


参考リンク

関連記事

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?