AIエージェントのコマンドを自動で承認する拒否リスト方式の4つの抜け道

English

この記事の目次

本記事の概要

AIエージェントがシェルのコマンドを実行する前に、デスクトップのアプリは承認のダイアログを出す。私が進めている取り組みでは、アプリの機能が動くかを判定する検査のスクリプトが、このダイアログのボタンを人の代わりに押す。押すかどうかの判定を、危ない語を並べて弾く拒否リスト方式で作ると、挙げ漏れた形のコマンドでもボタンを押してしまう。

2026-09-15に、マージの前に変更を読んだAIエージェントは、拒否リスト方式の実装に抜け道を見つけ、必須の指摘4件として挙げた。実装を書いたAIエージェントは、抜け道を1つずつ修正せず、押してよい形だけを並べる許可リスト方式の実装に作り直した。作り直した実装の単体テストには、ボタンを押さない例が18件ある。この18件を最初の実装に与えると、2026-10-12の照合では14件でボタンを押す判定になった。

許可リスト方式の実装は、列挙していない正しい操作でもボタンを押さない。ファイルを書くツールの権限リクエストでも、ボタンを押さない。そのため、実装を書いたAIエージェントは、アプリの中のAIエージェントに出す指示を変えた。

記事から得られること

AIエージェントがコマンドを実行する前に出る承認のボタンを、人の代わりに押すプログラムを書く人に向けた記事である。

本文の根拠は、私が進めている取り組みで2026-09-15に記録された作業と、2026-10-12の照合の結果と、同じ2026-10-12に確認した外部の資料である。

拒否リスト方式の自動承認が通した押さない例

人の代わりに承認のボタンを押すプログラムの最初の実装は、危ない語の一覧でコマンドを弾く方式だった。マージの前のレビューでは、この実装に抜け道が4つ見つかった。このプログラムを書いたAIエージェントは、抜け道を1つずつ直さず、ボタンを押してよい形だけを挙げる方式に判定を作り直した。作り直した後の単体テストにある押さない例18件を最初の実装に与えると、2026-10-12の照合では14件でボタンを押す判定になった。

デスクトップのアプリには、AIエージェントと会話する画面がある。アプリの中のAIエージェントがシェルのコマンドを実行する前に、アプリは人に承認を求める。このときアプリが出す画面を、承認のダイアログと呼ぶ。承認のダイアログには、1回だけ許可するボタン、設定を残して許可するボタン、拒否するボタンの3つが並ぶ。

アプリの画面をプログラムから操作して、アプリの機能が動くかを判定するPythonのスクリプトを、検査のスクリプトと呼ぶ。人の手を借りずに検査を流すため、検査のスクリプトは、人の代わりに1回だけ許可するボタンを押す。押すかどうかは、検査のスクリプトの中にある承認の判定の関数が決める。

検査のスクリプトと承認の判定の関数は、アプリの中のAIエージェントとは別のAIエージェントが書いた。このAIエージェントを、実装を書いたAIエージェントと呼ぶ。

2026-09-15にマージされたPRの初回のコミットでは、承認の判定の関数が、危ない語17件を並べた拒否リストを持っていた。この関数は、拒否リストの語を含むコマンドでは、ボタンを押さなかった。残りのコマンドでは、作業ディレクトリのパスか名前を含むかと、絶対パスを含むかで、押すかどうかを決めた。この関数を、拒否リスト方式の実装と呼ぶ。

同じPRの後のコミットで作り直した承認の判定の関数は、押してよい条件をすべて満たすコマンドでだけ、ボタンを押す。この関数を、許可リスト方式の実装と呼ぶ。

誤りは、人の承認を求めるアプリの作りでなく、承認のボタンを自動で押すプログラムの判定にあった。人の代わりにボタンを押すプログラムは、人による承認を置き換える。そのため、判定を1回誤ると、作業ディレクトリの外のファイルを読むコマンドや消すコマンドも、人の確認を経ずに実行される。

許可リスト方式の判定の流れ

ボタンを押してよい形だけを挙げる許可リスト方式の実装は、コマンドを区切りで分け、分けた部分ごとに条件を確かめる。分けた部分のすべてが記号と実行ファイルとパスの条件を満たすときに限り、1回だけ許可するボタンを押す。

アプリが出す承認のダイアログでは、「コマンド:」という見出しに続く行に、実行しようとするシェルのコマンドが書かれる。この行を、コマンドの行と呼ぶ。許可リスト方式の実装は、コマンドの行から取り出したコマンドを、シェルの区切りで1つずつの部分に分ける。この部分を、断片と呼ぶ。

決まった記号が断片に1つでも含まれていれば、許可リスト方式の実装は、そのコマンドではボタンを押さない。この記号を、禁止する記号と呼ぶ。判定のステップごとに、見るものと押さない場合を次の表に示す。

ステップ 見るもの 押さない場合
判定の対象 コマンドの行から取り出したコマンドの全体。取り出すときに文字を切り詰めない ほかのステップの条件を、コマンドの中の1か所でも満たさない場合
断片への分割 &&、||、セミコロン、パイプ、改行の5種類の区切り 断片の1つでも、ほかのステップの条件を満たさない場合
禁止する記号 ドル記号、バッククォート、チルダ、小なり記号、丸括弧の開き、丸括弧の閉じ、波括弧の開き、波括弧の閉じ、アスタリスク、疑問符、バックスラッシュ、アンパサンドの12種類 断片に1つでも含まれる場合
先頭の実行ファイル 断片の先頭の語と、cat、cd、git、lsなどの15件を並べた実行ファイルの許可リスト 先頭の語が15件のどれとも一致しない場合
パスの検証 >と>>の書き出し先、cdの行き先、断片のほかの引数。どれもパスとして扱い、絶対パスでないものは会話の作業ディレクトリを基準に解決する 解決した位置が、会話の作業ディレクトリの外になる場合

許可リスト方式の実装は、表の条件を1つでも満たさないコマンドではボタンを押さず、押さない理由を呼び出し元に返す。条件を満たすか判定できないコマンドでも、ボタンを押さない。

SaltzerとSchroederは、1975年の論文で、設計原則「Fail-safe defaults」を挙げた。この原則では、アクセスの判断を、除外でなく許可に基づかせる。許可リスト方式の実装の判定は、この原則に沿っている。

許可リスト方式の実装は、一覧に列挙していない操作では、正しい操作でもボタンを押さない。ファイルを書くツールの権限リクエストでも、ボタンを押さない。そのため、実装を書いたAIエージェントは、アプリの中のAIエージェントに出す指示の文を変えた。新しい指示の文では、リダイレクトを付けたechoでファイルを作らせ、パスを相対パスで書かせた。

拒否リスト方式の実装の条件と抜け道

危ない語の一覧で弾く拒否リスト方式の実装は、作業ディレクトリと絶対パスの条件でも、ボタンを押す範囲を絞っていた。それでも、この判定には4つの抜け道があった。どの抜け道でも、拒否リスト方式の実装は、コマンドの一部か承認の種類を調べないまま、ボタンを押すと判定した。抜け道のほかに、拒否リストに無い形の危ないコマンドでも、絶対パスの条件によってはボタンを押す判定になった。

拒否リスト方式の実装は、拒否リストの語を含まないコマンドに、2つの条件を順に当てた。まず、会話の作業ディレクトリのパスか名前がコマンドにあれば、ボタンを押した。どちらも無いときは、絶対パスを探した。スラッシュの直後に英数字かドットかアンダースコアが続く文字列を、絶対パスと見なした。この文字列が1つも無いときだけ、ボタンを押した。

2026-09-15に、マージの前に変更を読んで指摘を返すAIエージェントが、拒否リスト方式の実装を含むPRを読んだ。このAIエージェントを、レビューを担当するAIエージェントと呼ぶ。レビューを担当するAIエージェントは、承認の判定の関数にある抜け道を4つ挙げ、どれも必須の指摘にした。

承認のダイアログで、「コマンド:」という見出しに続けて実行しようとするコマンドを書いた行を、コマンドの行と呼ぶ。4つの抜け道ごとに、拒否リスト方式の実装が読まなかったものか見なかったものと、押す判定になる理由を次の表に示す。

抜け道 読まなかったもの、見なかったもの 押す判定になる理由
160字での切り詰め コマンドの行から取り出した文字列の、161字目より後ろ 切り詰めた後の文字列だけで判定するので、長いコマンドの後ろにつないだ別のコマンドを読まない
コマンドの行が無い本文 その承認がシェルのコマンドの承認かどうか 見出しの行をコマンドと見なして判定するので、ファイルの書き込みやMCPツールの権限リクエストで出る承認のダイアログでもボタンを押す
作業ディレクトリのパスか名前 パスか名前の後ろにつないだコマンドが指すパス パスか名前が1つでもあると外部のパスを検査しないので、作業ディレクトリへcdした後に秘密鍵を読むコマンドでもボタンを押す
シェルの記号 ホームディレクトリを表すチルダ、ドル記号と丸括弧で書くコマンドの置換、バッククォート、親ディレクトリをたどる2つのドット 記号を見ないので、拒否リストの語を使わずにホームディレクトリや別のコマンドの出力や親ディレクトリを指すコマンドでもボタンを押す

4つの抜け道とは別に、拒否リストの語の数も足りず、rmで始まる語はrm -rf /の1件だけだった。押さない例のうち、rm -fr /とrm -r /とrm -rf ~の3つは、この並びと形が違う。rm -fr /はオプションの文字の順序が違い、rm -r /はfのオプションが無い。rm -rf ~は、消す先がホームディレクトリである。そのため、3つともrm -rf /と一致せず、2026-10-12の照合ではボタンを押す判定になった。

CWE-184の説明によれば、許さない入力を並べた一覧に頼る保護の仕組みでは、一覧が不完全なことが弱点になる。同じページの対策の欄には、許さない入力の検出だけに頼らないよう求める記述がある。

参考にした資料