プログラミング
Show HN: Plimsoll、AIエージェントが書いたコードを実行するためのセルフホスト型サンドボックス
Show HN: Plimsoll, a self-hosted sandbox for code your AI agent writes (github.com)
要約
Plimsollは、AIエージェントが生成したコードのような信頼できないコードをサンドボックス内で実行し、そのサンドボックスの強度を示すセルフホスト型ツールです。プロセス、コンテナ、カーネル、VMの4段階の分離レベルを提供し、要求された最小強度を満たさない場合は実行を拒否します。また、コードのテスト実行や、安全なAPI呼び出しを可能にする機能も備えています。
全文翻訳
Plimsollは、AIエージェントが書いたコードのような信頼できないコードをサンドボックス内で実行し、その結果ごとにサンドボックスの強度を示します。リクエストは最小強度を要求でき、サンドボックスがそれより弱い場合は何も実行されません。この名前は、船体に描かれ誰でも確認できる積載量を示すPlimsoll線に由来しています。Plimsollは、分離レベルについても同様のことを行います。実行の周りの壁の強度を、最も弱いものから順にプロセス、コンテナ、カーネル、VMの4つのレベルのいずれかとして示します。このレベルは、データシートの文章ではなく、すべての結果の値です。リクエストは、受け入れ可能な最も弱いレベルであるフロアを設定でき、公式のGoクライアントは結果が返されたときにレベルを再度チェックします。
同じ仕組みにより、Plimsollは単にコードを隔離するだけでなく、エージェントが書いたコードに対して試験を実行することもできます。シミュレーションイメージに組み込まれたトライアルランナーは、プログラムが読み取れないシナリオに対してエージェントのプログラムを実行します。各シナリオは、プログラムが開始される前に削除されるファイルとしてランナーに届きます。すべての実行は、送信されたリクエストと返された結果のSHA-256ハッシュを返します。公式クライアントはこれらを再計算します(方法)。デーモン外の別のプログラム、plimsoll-attestは、チェックされた結果に署名し、1つ欠落した呼び出しチェーンを拒否し、実行を再生します。試験自体(シミュレートされたシステム、シナリオ、合格点)はあなたのものです。Plimsollは境界と証拠を提供します。
実際の実行例をご覧ください。AIエージェントがコントローラーを書きました。これは、各ティック(ここではシミュレーションの10ミリ秒ステップ)でシステムのステートを読み取り、それをプッシュする方法を決定するプログラムです。Plimsollはそれをシミュレートされたカートポールに対して実行しました。これは、ポールがヒンジで上部に接続されたレール上のカートであり、コントローラーはカートを左右にプッシュして直立に保ちます。シミュレーターは、ホストプログラム内で実行されるポータブルバイトコードであるWebAssemblyにコンパイルされ、サンドボックスイメージに組み込まれています。コントローラーは、呼び出し元が通常のプロジェクトAPIを通じて送信した唯一のファイルであり、コンテナレベルで実行されました。カートポールは、制御工学における標準的な教育問題です。ランナーは、イメージに組み込まれたプログラムで、シミュレーターに対してコントローラーを実行し、実行の軌跡、各ティックでのすべてのステートを記録し、それをフィンガープリントにハッシュします。SHA-256ハッシュなので、同じフィンガープリントは同一の数値を意味します。承認されたコントローラーを2回実行すると、2つのフィンガープリントが一致します。エージェントの最初のドラフトでは、2つのゲイン(その式での乗数)の符号が間違っていました。ポールは5.96秒で落下し、そのフィンガープリントは異なります。ライブ実行レポートを開いて、ブラウザで両方の実行を再生し、エージェントが書いたコントローラーを読み、ページが意図的に主張していないことを見てください。例の1回の実行は3回のサンドボックス実行を行います。承認されたコントローラーを2回、ドラフトを1回です。make docker-images && go run ./examples/oracle で再現できます。
それが実行することの2つのこと。それは壁を報告します。すべての結果は、実行が実際に得た分離レベルを示します。すべてのリクエストはフロアを設定でき、デーモンが満たせないフロアを設定したリクエストは、コードが実行される前に拒否されます。ErrInsufficientIsolationは何も実行されなかったことを意味します。res, err := provider.Sandbox.RunJavaScript(ctx, sandbox.Request{ Code: "console.log(6 * 7)", MinimumIsolation: sandbox.IsolationVM, // ErrInsufficientIsolation means nothing ran }) // res.Isolation reports the boundary that actually ran. 何も実行されなかったすべての拒否は、理由(リクエスト、パーミッション、プロトコル、サポートされていない、分離、環境、容量)とともにマークされます。sandbox.NotDispatchedReason(err)は、プロバイダー(コードを実行するバックエンド)が自身のプロセス内にあるか、デーモンの背後にあるかにかかわらず、マークを読み取ります。マークのないエラーは、コードの開始後に発生した可能性があるため、自動的に再試行するのは安全ではありません。マークは、クライアントのリクエストIDを返す応答にのみカウントされるため、あるリクエストの応答を別のリクエストの応答として提供するプロキシは、実行された呼び出しを「何も実行されなかった」と読み取らせることはできません(返されるもの、およびその意味)。レベルは、デーモンが収集した証拠です。その設定と自身の起動チェックです。これはアテステーション、つまりハードウェアからの実行中のソフトウェアの暗号証明ではありません。そして、ここのどのプロバイダーもそれをしません。docs/isolation-tiers.md に各レベルが何に基づいているかがリストされています。エージェントが書いたコードが、あなたの認証情報、APIのアドレス、または開いたネットワークアクセスを一切保持することなく、あなたのAPIを呼び出すことができます。グラントは、1回の実行に、指定されたルートの1つのAPIを呼び出す権限を与えます。コードは、Plimsollがサンドボックスに配置する小さなクライアントを通じて呼び出され、実際の呼び出しを行うサンドボックス外のPlimsollの一部であるブローカーが、呼び出し元と正確なルートをチェックし、認証情報を自身に添付します。認証情報は実行ごとに発行されます。Plimsollは実行ごとに1回要求するため、新しい短命のトークンであり、サンドボックスには入りません。コードがクライアントをスキップして手動で外部に呼び出しても、それ以上進むことはありません。なぜなら、ルールを強制するのはクライアントではなくブローカーだからです。docs/capability-grants.md を参照してください。
エージェントフレームワークから使用する。これらのエージェントフレームワークは、独自のツールまたはコードエグゼキューター(Trigger.dev、そのうちの1つはホストされたTypeScriptジョブランナー)を通じて、モデルのコードをPlimsollに渡すことができます。各行は、インストールするもの、ガイド、およびそのフレームワークのツールからサンドボックスへの、およびその逆の記録された呼び出し、拒否を含む再生ページの名前を示します。フレームワーク パッケージまたは追加 ガイド 実行を見る Vercel AI SDK @plimsollmark/client/ai-sdk Vercel AI SDKガイド(内部・ドキュメント→) Vercel AI SDKページ(内部・Plimsollサイト→) Google ADK plimsoll-client[adk] Google ADKガイド(内部・ドキュメント→) Google ADKページ(内部・Plimsollサイト→) Agno plimsoll-client[agno] Pythonクライアントガイド、AgnoおよびCrewAIツール(内部・ドキュメント→) Agnoページ(内部・Plimsollサイト→) CrewAI plimsoll-client[crewai] Pythonクライアントガイド、AgnoおよびCrewAIツール(内部・ドキュメント→) CrewAIページ(内部・Plimsollサイト→) Trigger.dev @plimsollmark/client/trigger Trigger.devガイド(内部・ドキュメント→) Trigger.devページ(内部・Plimsollサイト→) Mastra @plimsollmark/client/mastra TypeScriptクライアントガイド、Mastra(内部・ドキュメント→) Mastraページ(内部・Plimsollサイト→)
Trigger.devエージェントにコードツールを与える。TypeScriptアドオンは、Trigger.dev上のチャットエージェントにexecuteCodeツールを提供します。セッションを持つデーモンでは、いくつかの呼び出しのために保持される1つのサンドボックスがあり、各呼び出しは、その会話のために保持されているインタープリター内で実行されるセルです。変数やファイルは次の呼び出しまで存続できます。各ツール結果は、それらが存続できるかどうか、この呼び出しが新規に開始されたかどうか、それが実行された分離レベル、および実行レコードのSHA-256(クライアントが返す前にチェックした送信および返信の領収書)を示します。呼び出し元は最小レベルを設定するため、より弱いプロバイダーはコードを実行する前に拒否します。機能とデプロイメントガイドで正確な保証と本番フロアを確認してください。デプロイ可能なTrigger.devスターターには、チャットエージェントと、AIモデルの呼び出しを行わずに、実際のデーモンを通じて2つのセルが1つのインタープリターを共有することを示すタスクがあります。
1分で何かを実行する。この例では、デーモン、Docker、認証情報、ネットワークは不要です。wasmプロバイダーを使用します。これは、QuickJS(WebAssemblyにコンパイルされた小さなJavaScriptエンジン)上でJavaScriptを実行し、自身のプロセス内で実行されます。Goモジュールのクローンとフェッチはネットワークを使用します。git clone https://github.com/plimsollmark/plimsoll && cd plimsoll go run ./examples/minimal provider wasm isolation process exit code 0 timed out false truncated stdout=false stderr=false duration 304ms stdout {"Engineering":59000000,"Sales":20300000,"Operations":9800000}
分離線は、例自体のテキストからではなく、実行結果から来ています。processはオペレーティングシステムレベルの壁が全くないことを意味します。敵対的なコードには使用しないでください。gVisorがインストールされている場合、同じスニペットがカーネルレベルで実行されます。gVisorは、コンテナとマシンカーネルの間のレイヤーであり、コンテナのオペレーティングシステム自体へのリクエストを処理するため、コードはカーネルと直接通信しません。sudo ./docker/in