このコンテンツは自動機械翻訳サービスによる翻訳版であり、皆さまの便宜のために提供しています。原本の英語版と異なる誤り、省略、解釈の微妙な違いが含まれる場合があります。ご不明な点がある場合は、英語版原本をご確認ください。
AI大規模言語モデルやOpenCode、Claude Codeのような活用がますます能力を高めるにつれて、チャットメッセージ、Kanbanの更新、バイブコーディングUI、ターミナルセッション、GitHubのコメントなどに応答してサンドボックスエージェントを起動するユーザーが増えています。
サンドボックスは、単純なコンテナを超えた重要なステップです。それは、いくつかのことを可能にします。
セキュリティ:信頼できないエンドユーザー(または不正なLLM)は、サンドボックスで実行される可能性があり、ホストマシンやそれに沿って実行されている他のサンドボックスを侵害することはありません。これは従来、(常にそうとは限りませんが) マイクロVMで実現されます。
スピード:エンドユーザーは、新しいサンドボックスをすばやく選択し、以前使用したサンドボックスから状態をすぐに復元できる必要があります。
制御:信頼できるプラットフォームは、サンドボックスの信頼できないドメイン内でアクションを実行できる必要があります。これは、サンドボックスにファイルをインストールしたり、アクセスするリクエストを制御したり、特定のコマンドを実行したりすることを意味します。
本日、サンドボックスおよびすべてのコンテナへの、もう一つの重要な制御コンポーネントであるOutbound Workersを追加できることを嬉しく思います。これらは、プログラム可能なエグレスプロキシで、サンドボックスを実行するユーザーがさまざまなサービスに簡単に接続し、可観測性を追加し、エージェントにとって柔軟で安全な認証を追加することができます。
Outbound Workersを使って秘密鍵をヘッダーに追加する方法を簡単に説明します。
class OpenCodeInABox extends Sandbox {
static outboundByHost = {
"github.com": (request, env, ctx) => {
const headersWithAuth = new Headers(request.headers);
headersWithAuth.set("x-auth-token", env.SECRET);
return fetch(request, { headers: headersWithAuth });
}
}
}
サンドボックスで実行されているコードが “github.com” にリクエストを行うと、リクエストはハンドラを介してプロキシされます。これにより、各リクエストに対して、ログ記録、変更、取り消しなど、何でもすることができます。この場合、シークレット(詳細は後述)を安全に挿入することができます。プロキシはサンドボックスと同じマシンで実行され、分散状態にアクセスでき、簡単なJavaScriptで簡単に変更できます。
当社は、特にエージェントの認証に関して、Sandboxに追加される可能性を非常に楽しみにしています。詳しく説明する前に、従来の認証形式と、弊社が優れたものがあると考える理由について簡単に説明します。
エージェント認証の中心的な問題は、ワークロードを完全に信頼することができないことです。当社のLLMは(少なくともまだ)悪意のあるものではありませんが、データを不適切に使用したり、不適切なアクションを実行したりしないように、保護を適用できる必要があります。
エージェントに認証を提供するための一般的な方法がいくつかありますが、それぞれに欠点があります。
Standard APIトークンは最も基本的な認証方法で、通常は環境変数か、インストールされたシークレットファイルを介してアプリケーションに挿入されます。これは確かに最もシンプルな方法ですが、安全性は最も低いです。リクエストを行っている間に、サンドボックスが何らかの形で侵害されたり、誤ってトークンが流出したりする可能性はないと信用しなければなりません。エージェントを完全に信頼できないため、トークンの有効期限とローテーションを設定する必要があり、面倒な作業になる可能性があります。
OIDCトークンなどのワークロードIDトークンは、この問題の一部を解決できます。エージェントに一般的な権限でトークンを付与するのではなく、そのアイデンティティを証明するトークンを付与することができます。現在、エージェントはトークンを使用していくつかのサービスに直接アクセスするのではなく、IDトークンを非常に短期間のアクセストークンに交換することができます。OIDCトークンは、特定のエージェントのワークフローが完了した後に無効にすることができ、有効期限の管理も容易になります。ワークロードIDトークンの最大の欠点の1つは、統合の柔軟性に欠ける可能性があることです。多くのサービスにはOIDCの一流のサポートがないため、アップストリームのサービスとの統合を機能させるためには、プラットフォームは独自のトークン交換サービスを展開する必要があります。これにより、導入が困難になります。
カスタムプロキシは最大限の柔軟性を提供し、ワークロードIDトークンと組み合わせることができます。サンドボックスのエグレスの一部またはすべてを信頼できるコードを介して通過させることができれば、必要なルールを挿入できます。エージェントが通信しているアップストリームサービスのRBACストーリーが異なり、きめ細かい許可を提供できないかもしれません。問題ありません。コントロールと許可を自分で書けば良いのです!これは、きめ細かい制御でロックダウンする必要があるエージェントに最適なオプションです。しかし、サンドボックスのトラフィックをすべて傍受するにはどうすればいいのでしょうか?動的で簡単にプログラム可能なプロキシを設定するにはどうすればよいでしょうか?トラフィックを効率的にプロキシする方法は?これらは解決するのは簡単ではありません。
このような不完全な方法を念頭に置くと、理想的な認証メカニズムはどのようなものになるでしょうか?
理想的には、次のような状態です:
Zero Trust.信頼できないユーザーにトークンが付与されることは決してありません。
シンプルです。作成が容易。トークンの生成、ローテーション、復号化という複雑なシステムは不要です。
柔軟性。必要なきめ細かいアクセスの提供を上流システムに依存することはありません。どんなルールでも適用できます。
ID認識型。呼び出しを行うサンドボックスを特定し、そのサンドボックスに特定のルールを適用することができます。
観測可能。どのような呼び出しが行われているかに関する情報を簡単に収集することができます。
パフォーマンス。一元化された、または遅い信頼できる情報源にラウンドトリップするわけではありません。
透明。サンドボックス化されたワークロードは、それを知る必要はありません。上手く機能しているのです。
ダイナミック。ルールはその場で変更できます。
Cloudflareは、Outbound Workers for Sandboxsがこれらすべてに適合すると考えています。方法を見てみましょう。
まず、リクエストをログに記録し、特定のアクションを拒否するという非常に基本的な例を見ていきます。
この場合は、サンドボックスからすべての発信HTTPリクエストを傍受するアウトバウンド関数を使用します。数行のJavaScriptで、GETだけを実行し、ログに記録して、許可されていないメソッドを拒否することが簡単にできます。
class MySandboxedApp extends Sandbox {
static outbound = (req, env, ctx) => {
// Deny any non-GET action and log
if (req.method !== 'GET') {
console.log(`Container making ${req.method} request to: ${req.url}`);
return new Response('Not Allowed', { status: 405, statusText: 'Method Not Allowed'});
}
// Proceed if it is a GET request
return fetch(req);
};
}
このプロキシはWorkers上で実行され、サンドボックス化されたVMと同じマシン上で実行されます。Workersは応答時間を短縮するために構築されており、キャッシュされたCDNトラフィックの前面に位置することが多いため、追加の遅延は極めて最小限に抑えられます。
これはWorkers上で実行されているため、すぐに観測可能性が得られます。Workersダッシュボードでログやアウトバウンドリクエストを表示したり、お好みのアプリケーションパフォーマンス監視ツールにエクスポートしたりできます。
Zero Trustクレデンシャルインジェクション
これを使って、エージェントにZero Trust環境を強制する方法とは?プライベートなGitHubインスタンスにリクエストを作成したいが、LLMにはプライベートトークンにアクセスさせたくない場合を想像してみましょう。
outboundByHostを使用して、特定のドメインまたはIPに対する関数を定義できます。今回、ドメインが「my-internal-vcs.dev」の場合、保護されたクレデンシャルを挿入します。サンドボックス化されたエージェントは、これらのクレデンシャルにアクセスすることはできません。
class OpenCodeInABox extends Sandbox {
static outboundByHost = {
"my-internal-vcs.dev": (request, env, ctx) => {
const headersWithAuth = new Headers(request.headers);
headersWithAuth.set("x-auth-token", env.SECRET);
return fetch(request, { headers: headersWithAuth });
}
}
}
また、コンテナのIDに基づいて、応答の条件を簡単に設定することもできます。すべてのサンドボックスインスタンスに同じトークンを注入する必要はありません。
static outboundByHost = {
"my-internal-vcs.dev": (request, env, ctx) => {
// note: KV is encrypted at rest and in transit
const authKey = await env.KEYS.get(ctx.containerId);
const requestWithAuth = new Request(request);
requestWithAuth.headers.set("x-auth-token", authKey);
return fetch(requestWithAuth);
}
}
最後の例でお分かりのように、Outbound Workersを使用するもう1つの大きな利点は、Workersエコシステムへの統合が容易になることです。以前は、ユーザーがR2にアクセスする場合、R2認証情報を挿入し、コンテナからパブリックR2 APIを呼び出す必要がありました。KV、エージェント、その他のコンテナ、その他のWorkerサービス、などでも同様です。
今、任意のバインディングをOutbound Workersから呼び出すだけです。
class MySandboxedApp extends Sandbox {
static outboundByHost = {
"my.kv": async (req, env, ctx) => {
const key = keyFromReq(req);
const myResult = await env.KV.get(key);
return new Response(myResult);
},
"objects.cf": async (req, env, ctx) => {
const prefix = ctx.containerId
const path = pathFromRequest(req);
const object = await env.R2.get(`${prefix}/${path}`);
const myResult = await env.KV.get(key);
return new Response(myResult);
},
};
}
トークンを解析したりポリシーを設定したりする代わりに、コードと必要なロジックを使用して簡単にアクセスの条件を設定できます。R2の例では、サンドボックスのIDを使用して、アクセスの範囲を簡単に拡大することができました。
ネットワーク制御も動的である必要があります。多くのプラットフォームでは、コンテナとVMネットワーキングの設定は静的で、次のようになります。
{
defaultEgress: "block",
allowedDomains: ["github.com", "npmjs.org"]
}
これなら何もないよりはいいのですが、もっと良い方法があります。多くのサンドボックスでは、開始時にポリシーを適用し、特定の操作が実行された後で別のポリシーに上書きしたい場合があります。
たとえば、サンドボックスを起動し、NPMやGithubを介して依存関係を取得し、その後、エグレスをロックダウンすることができます。そうすることで、ネットワークを開く時間ができるだけ短くなります。
これを実現するために、outboundHandlersを使用できます。これにより、setOutboundHandlerメソッドを使用してプログラム的に適用できる任意のアウトバウンドハンドラーを定義できます。これらのそれぞれはパラムを利用するので、コードから動作をカスタマイズすることができます。この場合、カスタム「allowHosts」ポリシーでいくつかのホスト名を許可し、その後はHTTPをオフにします。
class MySandboxedApp extends Sandbox {
static outboundHandlers = {
async allowHosts(req, env, { params }) {
const url = new URL(request.url);
const allowedHostname = params.allowedHostnames.includes(url.hostname);
if (allowedHostname) {
return await fetch(newRequest);
} else {
return new Response(null, { status: 403, statusText: "Forbidden" });
}
}
async noHttp(req) {
return new Response(null, { status: 403, statusText: "Forbidden" });
}
}
}
async setUpSandboxes(req, env) {
const sandbox = await env.SANDBOX.getByName(userId);
await sandbox.setOutboundHandler("allowHosts", {
allowedHostnames: ["github.com", "npmjs.org"]
});
await sandbox.gitClone(userRepoURL)
await sandbox.exec("npm install")
await sandbox.setOutboundHandler("noHttp");
}
これはさらに拡張することも可能です。エージェントはエンドユーザーに対して、「cloudflare.comへのPOSTリクエストを許可しますか?」といった質問をするかもしれません。アクセスできるように\nしたりすることもできます。動的Outbound Workersを使用すると、その場でサンドボックスルールを簡単に変更して、このレベルの制御を提供することができます。
リクエストを許可または拒否する以上の有用な操作を行うには、コンテンツへのアクセスが必要です。つまり、HTTPSリクエストを行う場合、Workersプロキシによって復号化される必要があるのです。
これを実現するために、各サンドボックスインスタンスに一意のエフェメラル認証局(CA)とプライベートキーを作成し、CAをサンドボックスに配置します。デフォルトでは、サンドボックスインスタンスはこのCAを信頼しますが、標準的なコンテナインスタンスは、たとえばsudo update-ca-certificatesを呼び出すことによって、信頼することを選択できます。
export class MyContainer extends Container {
interceptHttps = true;
}
MyContainer.outbound = (req, env, ctx) => {
// All HTTP(S) requests will trigger this hook.
return fetch(req);
};
TLSトラフィックは、TLSハンドシェイクを実行することにより、Cloudflareの分離されたネットワークプロセスによってプロキシされます。一過性の一意のプライベートキーからエッジCAを作成し、ClientHelloから抽出されたSNIを使用します。次に、同じマシンで設定済みのWorkerを呼び出し、HTTPSリクエストを処理します。
一過性のプライベートキーとCAは、コンテナランタイムのサイドカープロセスから離れることはなく、他のコンテナサイドカープロセスで共有されることはありません。
これを有効にすると、Outbound Workersは真に透明性のあるプロキシとして機能します。サンドボックスは、特定のプロトコルまたはドメインを認識する必要がありません。すべてのHTTPおよびHTTPSトラフィックは、フィルタリングまたは変更のためにアウトバウンドハンドラーを通過します。
上記の機能をContainerとSandboxの両方で有効にするために、ctx.containerオブジェクトに新しいメソッドを追加しました。interceptOutboundHttpとinterceptOutboundHttpsは、特定のホスト名(基本的なglobパターン一致を含む)、IP範囲での発信リクエストをインターセプトし、すべての発信リクエストをインターセプトするために使用できます。これらのメソッドは、WorkerEntrypoint を使用して呼び出されます。WorkerEntrypointは、Outbound Workersの玄関口として設定されます。
export class MyWorker extends WorkerEntrypoint {
fetch() {
return new Response(this.ctx.props.message);
}
}
// ... inside your container DurableObject ...
this.ctx.container.start({ enableInternet: false });
const outboundWorker = this.ctx.exports.MyWorker({ props: { message: 'hello' } });
await this.ctx.container.interceptOutboundHttp('15.0.0.1:80', outboundWorker);
// From now on, all HTTP requests to 15.0.0.1:80 return "hello"
await this.waitForContainerToBeHealthy();
// You can decide to return another message now...
const secondOutboundWorker = this.ctx.exports.MyWorker({ props: { message: 'switcheroo' } });
await this.ctx.container.interceptOutboundHttp('15.0.0.1:80', secondOutboundWorker);
// all HTTP requests to 15.0.0.1 now show "switcheroo", even on connections that were
// open before this interceptOutboundHttp
// You can even set hostnames, CIDRs, for both IPv4 and IPv6
await this.ctx.container.interceptOutboundHttp('example.com', secondOutboundWorker);
await this.ctx.container.interceptOutboundHttp('*.example.com', secondOutboundWorker);
await this.ctx.container.interceptOutboundHttp('123.123.123.123/23', secoundOutboundWorker);
Workersへのプロキシはすべて、サンドボックスVMを実行している同じマシン上でローカルに行われます。コンテナとWorkerの間の通信は「認証されていない」ものですが、安全です。
これらのメソッドは、コンテナが開始する前または後、接続がまだオープン中であっても、いつでも呼び出すことができます。複数のHTTPリクエストを送信する接続は、新しいエントリポイントを自動的に選択するため、Outbound Workersを更新しても、既存のTCP接続が切断されたり、HTTPリクエストが中断されたりすることはありません。
wrangler devを使用したローカル開発は、エグレス傍受もサポートしています。これを可能にするために、ローカルコンテナのネットワークネームスペース内にサイドカープロセスを自動的に生成します。私たちはこのサイドカーコンポーネントをproxy-everythingと呼んでいます。proxy-everythingが接続されると、適切なTPROXY nftableルールが適用され、ローカルコンテナからworkerd(CloudflareのオープンソースJavaScriptランタイム)にトラフィックがルーティングされ、アウトバウンドWorkerを実行します。これにより、ローカル開発エクスペリエンスが本番環境で発生することを反映させることができるため、テストと開発はシンプルのままとなります。
Giving Outbound Workers a try
Cloudflare Sandboxesをまだお試しでない方は、利用開始ガイドをぜひご覧ください。現在既にContainersまたはSandboxesをご利用の方は、ドキュメントをご覧になり、@cloudflare/[email protected]または@cloudflare/[email protected]にアップグレードして、今すぐOutbound Workersを使い始めてください。