`pip install dspy[deno]`だけでインストールが完結──DSPyがコード実行サンドボックスの穴を5つ塞いだ話
LLMフレームワークDSPyはv3.3.1(2026年8月21日)で、コード実行サンドボックス`PythonInterpreter`のセットアップを`pip install dspy[deno]`一発で完結させる「マネージドランタイム」を追加した。公式リリースノートは同時に、JSON-RPC応答のすり替えや、ゲストコードがホスト側ツールになりすます手口など、具体的な分離の穴を列挙している。
![`pip install dspy[deno]`だけでインストールが完結──DSPyがコード実行サンドボックスの穴を5つ塞いだ話](/images/thumbs/dspy-pythoninterpreter-deno-runtime.png)
目次
LLMにPythonコードを実際に実行させて答え合わせをする——DSPyのPythonInterpreterは、そのためのサンドボックス実行環境だ。v3.3.1(2026年8月21日公開)は、このインタープリタのインストール方法と分離(isolation)の堅牢性を、まとめて手直しするリリースになった。
3行まとめ
- DSPy v3.3.1で
PythonInterpreterがpip install "dspy[deno]"一発でセットアップ完結。同時にJSON-RPC応答の混線・リクエストID予測・ホストツールなりすましなど5つの分離の穴を修正した- GitHub APIで個別PRを確認すると、JSON-RPC混線の原因は「Denoサンドボックス内の未処理の非同期例外がid: nullのJSON-RPCエラーを出力し、ホスト側がそれを本来の応答と誤認して以降のリクエストが1つずつズレる」という具体的なバグ(PR #10190、Issue #10165をクローズ)だった
- このリリースを構成するPRは合計12件(#10119〜#10255)。マネージドランタイムはDenoの公式PyPI配布バイナリ(
>=2.4.5,<3)を使っている(PR #10186)
インストールが1コマンドで完結するように
公式リリースノートは、まずセットアップの簡略化から紹介している。
"
PythonInterpreternow has an optional managed runtime installation:"
pip install "dspy[deno]"
"DSPy prefers that managed binary when present, while continuing to support system Deno 2.x and an explicit custom
deno_command. The default path pins Pyodide, validates Deno>=2.0.0,<3.0.0, and ignores ambient Node and Deno project configuration so nearby application files cannot change sandbox startup."
(PythonInterpreterは、オプションのマネージドランタイムインストールに対応した。DSPyは、そのマネージドバイナリが存在する場合はそれを優先しつつ、システムのDeno 2.xや明示的にカスタム指定したdeno_commandも引き続きサポートする。既定のパスはPyodideのバージョンを固定し、Denoが>=2.0.0,<3.0.0であることを検証し、周囲のNode・Denoのプロジェクト設定を無視する。これにより、近くにあるアプリケーションのファイルがサンドボックスの起動を変えてしまうことができないようにしている)
PythonInterpreterはPyodide(ブラウザ・サーバーサイドで動くPython実行環境)をDeno(JavaScript/TypeScriptランタイム)上で動かす仕組みだとみられ、これまではDenoを別途システムにインストールしておく必要があった。今回のマネージドランタイムにより、pip installのオプション1つでその手間が解消される。「周囲のプロジェクト設定を無視する」という一文は、同じディレクトリに別のNode/Denoプロジェクトの設定ファイルが存在していても、サンドボックスの起動条件がそれに引きずられないようにする、という細かい配慮だ。
実行の整合性・分離を塞いだ「穴」が具体的に列挙されている
このリリースで最も読みごたえがあるのは、公式リリースノートが「実行整合性と分離のギャップ」として、修正した具体的な問題点を箇条書きで並べている部分だ。
"The interpreter also closes several execution-integrity and isolation gaps:" "- unsolicited sandbox diagnostics can no longer desynchronize JSON-RPC replies;" "- request IDs are unpredictable, and recursive execution through one of an interpreter's own host tools is rejected;" "- bundled runtime files are protected and Deno-cache access is revoked after startup;" "- mounted files with distinct host paths cannot silently collide at the same sandbox basename; and" "- guest code cannot change host-tool identity by mutating JavaScript globals or prototypes."
(インタープリタは、いくつかの実行整合性・分離のギャップも塞いだ。要求されていないサンドボックスの診断情報が、JSON-RPCの応答を混線させることはもうできない。リクエストIDは予測不可能になり、インタープリタ自身のホストツールの1つを経由した再帰的な実行は拒否される。バンドルされたランタイムファイルは保護され、起動後にDenoキャッシュへのアクセスは剥奪される。異なるホストパスを持つマウント済みファイルが、サンドボックス内の同じベース名で密かに衝突することはなくなった。ゲストコードは、JavaScriptのグローバル変数やプロトタイプを書き換えることで、ホストツールの正体を変えることができなくなった)
これらは1つ1つが、サンドボックス実装にありがちな具体的な攻撃・混線パターンへの対処だ。特に「JSON-RPC応答のすり替え」「リクエストIDの予測可能性」「ホストツールへのなりすまし」は、サンドボックス内で動くコード(ゲストコード、つまりLLMが生成したコード)が、意図せず・あるいは意図的に、サンドボックスの外側とやり取りする通信プロトコルに介入する余地を塞ぐ変更だと読める。
リリースノートの箇条書きだけでは分からない、それぞれの穴の実体をGitHub APIで個別PRを開いて確認すると、次のように対応していた。
| リリースノートの記述 | 対応PR | 実体 |
|---|---|---|
| JSON-RPC応答のすり替えを防止 | #10190(Issue #10165をクローズ) | Denoサンドボックス内で未処理の非同期例外が発生すると、runner.jsがid: nullのJSON-RPC風エラーを出力し、ホスト側がそれを本来の応答と誤認。以降の全リクエストが1つずつズレ、正常なインタープリタが「Response ID mismatch」で強制終了させられていた |
| リクエストID予測不可・再帰実行の拒否 | #10208 | 逐次カウンタ式だったリクエストIDを128bitの予測不能な値に変更。ゲストコードのプロトタイプ/グローバル変数改竄下でも「ラッパーA→ツールA」の対応関係を維持し、同一インタープリタを再帰的に呼び出す実行を伝送層の混線が起きる前に拒否するようにした |
| マウントファイルの衝突防止 | #10205 | PythonInterpreterはホスト側のファイルパスをベース名だけに平坦化してサンドボックスにマウントするため、first/config.jsonとsecond/config.jsonのような別ファイルが同じゲストファイルに衝突しうる問題を解消 |
| Denoキャッシュの隔離 | #10206 | Pyodideの読み込み後、ランナーのキャッシュ読み取り権限を剥奪し、ゲストコードが無関係なキャッシュ内容を読めないようにした |
(出典:dspy 3.3.1リリースノート、および各PRのGitHub API本文)
実行の可観測性も強化
もう1つの柱は、インタープリタの実行状況を外部から追跡できるようにする変更だ。
"DSPy's callback API now exposes the complete interpreter lifecycle:" "- interpreter execution start and end;" "- sandbox-to-host tool-call start and end;" "- interpreter process startup and shutdown." "Events retain callback ancestry across modules, interpreters, tools, and LM calls. End callbacks receive terminating
BaseExceptionvalues such as cancellation and interruption instead of incorrectly reporting those operations as successful."
(DSPyのコールバックAPIは、インタープリタのライフサイクル全体を公開するようになった:インタープリタの実行開始・終了、サンドボックスからホストへのツール呼び出しの開始・終了、インタープリタプロセスの起動・シャットダウン。イベントは、モジュール・インタープリタ・ツール・LM呼び出しをまたいでコールバックの親子関係を保持する。終了コールバックは、キャンセルや中断といったBaseExceptionを受け取れるようになり、それらの操作を誤って「成功」と報告することがなくなった)
「キャンセルされた処理を誤って成功と報告していた」というのは、地味だが実務上は厄介なバグだ。ログや監視の仕組みがこの報告を信頼していた場合、実際には中断された処理が正常終了したものとして扱われてしまう。
Flexが動く土台を固める意味合い
筆者はコード実行サンドボックスを自分で実装したことはないが、この変更点リストを読むと、「LLMに実際にコードを実行させる」という一見便利な機能が、裏側では相当な数の具体的な攻撃・誤動作パターンへの対処を積み重ねて成立していることがよく分かる。DSPyのFlex機能(本サイトの別記事で取り上げた、プログラム構造自体をGEPAに最適化させる仕組み)も、このPythonInterpreterサンドボックスの上で生成コードを実行する設計になっている。つまり今回の堅牢化は、Flexのような「LLMにコードを書かせて実行する」新機能群の土台を固める意味合いも持っていると考えられる。
Issue #10165自体の内容とホストツールなりすましの再現条件は追えていない
個別PRを開いたことで、JSON-RPC混線・リクエストID・ファイルマウント衝突・Denoキャッシュ隔離の4つについては具体的な原因が確認できた。一方、PR #10190が「クローズした」とするIssue #10165そのもの(誰が・どういう状況で最初にこの不具合を報告したか)は、Issueページ自体を今回は開いておらずPR本文の記述をそのまま引いている。また「ゲストコードがJavaScriptのグローバル変数やプロトタイプを書き換えてホストツールの正体を変える」というなりすまし手口についても、実際にどのようなコードでそれが再現できたのかという具体例までは、PR #10208の要約以上には踏み込んで確認していない。マネージドランタイムのバイナリサイズやインストール時間への影響についても、公式リリースノート・確認した各PRのいずれにも記載がなかった。
関連記事
出典・参照資料
AIニュースの解説を動画でも
YouTubeでは注目ニュースの背景を解説し、Xでは新着記事をお知らせしています。
コメント
まだコメントはありません。最初のコメントを書いてみませんか?
AIについて聞きたいことはありますか?
質問箱で無料で受け付けています。回答は公開され、他の方の参考にもなります。
質問箱を見る →新しい記事をメールで受け取る
AIの新しい発表を、出典付きで整理して届けます。