[解説] PraisonAI codeModeの深刻な脆弱性:AIエージェントのRCEリスクとJavaScriptサンドボックスの危険性
はじめに:AIエージェントが抱えるRCEの危険性
こんにちは、フロントエンドエンジニアの皆さん。今回は、AIエージェント界隈で話題となっているGHSA-p69m-4f92-2v84という深刻な脆弱性について解説します。PraisonAIの`codeMode`ツールが対象ですが、JavaScriptでコードを実行する際のサンドボックスの難しさを浮き彫りにするものであり、Node.js環境で何らかのユーザー生成コードを実行する可能性がある全ての方にとって、非常に重要な情報です。
この脆弱性は「Critical」と評価されており、AIエージェントに仕向けられた悪意のあるプロンプトによって、ホストシステム上で任意のコードが実行されてしまう「リモートコード実行(RCE)」の危険性があります。その根源は、JavaScriptの`new Function()`と`with(sandbox)`パターンによる不十分な分離にありました。
何が問題なのか?:脆弱性の具体的な仕組み
PraisonAIの`codeMode`ツールは、LLMが生成したコードを`src/praisonai-ts/src/tools/builtins/code-mode.ts`内で実行する際、`new Function()`と`with(sandbox)`パターンを使用してサンドボックス環境を提供しようとしました。しかし、このサンドボックスは以下の3つの弱点により、簡単に回避されてしまいます。
JavaScriptの`with`文は、指定されたオブジェクトをスコープチェーンの先頭に追加しますが、グローバルオブジェクトへのアクセスを完全に遮断するものではありません。開発者は`process: undefined`や`require: undefined`を設定することでグローバルオブジェクトへのアクセスをブロックしようとしましたが、これは簡単に破られます。
具体的には、`Function('return this')()`のようなコードを実行することで、グローバルオブジェクトへの参照を回復できてしまいます。フロントエンドの皆さんならご存知の通り、`this`の参照は実行コンテキストによって変わりますが、グローバルスコープで`new Function()`を使って実行された場合、`this`はグローバルオブジェクト(Node.jsでは`global`または`globalThis`)を指します。こうなると、サンドボックスはもはや意味をなしません。
悪意のあるコードを防ぐために正規表現ベースのブラックリストが用いられていましたが、これは容易に回避されます。例えば、`require('child_process')`という文字列はブロックされても、`require('child_' + 'process')`のように文字列を連結するだけで正規表現による検出をすり抜けてしまいます。これは、あらゆる悪意のあるパターンを網羅することの難しさを示しています。
`Function('return this')()`のような、サンドボックスを破るための決定的な構文がブラックリストのパターンに含まれていませんでした。これにより、攻撃者は簡単にグローバルオブジェクトにアクセスし、システムコマンドを実行する足がかりを得ることが可能になります。
想定される影響:なぜこれほど深刻なのか
攻撃者がこの脆弱性を悪用した場合、ホストシステム上で完全に任意のコードを実行できてしまいます。これにより、以下のような深刻な被害が発生する可能性があります。
<ul><li>プロセスユーザーがアクセス可能な<strong>あらゆるファイルの読み書き</strong></li><li>`child_process`モジュールを通じた<strong>任意のシステムコマンドの実行</strong></li><li>APIキー、トークン、認証情報などの機密情報を含む<strong>環境変数の外部への流出</strong></li><li>起動ファイルへの書き込みによる<strong>持続的なバックドアの設置</strong></li><li>コンテナ化された環境内での<strong>横方向への侵入</strong></li></ul>
AIエージェントが機密情報を含む環境で動作している場合、その情報が漏洩したり、システム自体が乗っ取られたりする危険性があります。これは、AIシステムの信頼性を根底から揺るがす事態です。
今すぐ取るべき対策
現在の`with(sandbox)`とブラックリストの組み合わせによるサンドボックスは根本的に安全ではありません。正規表現の改善だけでは解決できないため、以下のいずれかの方法でサンドボックス機構を置き換えることを強く推奨します。
<ul><li><strong>`vm`モジュールによる適切なコンテキスト分離の利用:</strong> Node.jsに標準搭載されている`vm`モジュールを使用し、`createContext`と`runInContext`を使って、安全なグローバル変数のみを許可した独立したコンテキストでコードを実行します。これは、より厳格なスコープ分離を実現するためのNode.jsにおける標準的なアプローチです。</li><li><strong>`isolated-vm`の利用:</strong> 真のプロセスレベルでの分離が必要な場合は、`isolated-vm`のようなライブラリを使用して、V8の独立したアイソレート(分離されたV8インスタンス)でコードを実行します。これにより、メモリレベルでの高い分離が実現され、より堅牢なサンドボックスを構築できます。</li><li><strong>サブプロセスでのコード実行:</strong> コードを分離されたサブプロセスで実行し、クリーンな環境とリソース制限を設けます。例えば、Pythonのサンドボックス化されたコード実行パターンを参考に、異なるプロセスでコードを実行し、プロセス間の通信で結果をやり取りする方法です。</li></ul>
もし、上記の根本的な対策への移行がすぐにできない場合、一時的な緊急対応としてブラックリストを強化することも考えられます。具体的には、`Function(`、`new Function`、`constructor`、`__proto__`、`prototype`、`return this`、`return global`、`global`、`globalThis`、`window`などのパターンを追加して検出を強化する必要があります。
しかし、このブラックリスト方式は本質的に脆弱であり、常に新たな回避策が見つかる可能性が高いことに留意してください。これは「猫とねずみのゲーム」であり、時間稼ぎにしかならないことを理解しておくべきです。
まとめ
今回のPraisonAIの脆弱性は、JavaScript環境でユーザー生成コードや信頼できないコードを実行する際のサンドボックス実装の難しさと、その重要性を改めて浮き彫りにしました。AIエージェントのような先進的な技術を扱う際には、その背後で動作するコードのセキュリティに対する深い理解が不可欠です。
フロントエンドエンジニアの皆さんにとっても、Node.jsベースのバックエンドやSSR環境など、コード実行が関わる場面では同様のリスクが潜んでいます。常に最新のセキュリティ情報を追いかけ、堅牢なシステム設計を心がけましょう。