Sandbox SDK は Containers の上に構築されています。各サンドボックスは独自の VM で動き、強い分離を提供します。
各サンドボックスは別の VM で動き、完全に分離されます。
- ファイルシステムの分離 - サンドボックスは、ほかのサンドボックスのファイルにアクセスできません
- プロセスの分離 - あるサンドボックスのプロセスは、ほかのサンドボックスのプロセスを見たり影響したりできません
- ネットワークの分離 - サンドボックスは別々のネットワークスタックを持ちます
- リソース制限 - CPU、メモリ、ディスクのクォータはサンドボックスごとに適用されます
基盤となるコンテナプラットフォームのセキュリティ詳細は Containers のアーキテクチャ を参照してください。
1 つのサンドボックス内のコードは、リソースを共有します。
- ファイルシステム - すべてのプロセスが同じファイルを見ます
- プロセス - すべてのセッションがすべてのプロセスを見られます
- ネットワーク - プロセスは localhost 経由で通信できます
完全に分離するには、ユーザーごとに別のサンドボックスを使います。
// Good - Each user in separate sandbox
const userSandbox = getSandbox(env.Sandbox, `user-${userId}`);
// Bad - Users sharing one sandbox
const shared = getSandbox(env.Sandbox, 'shared');
// Users can read each other's files!コマンドで使う前に、ユーザー入力を必ず検証してください。
// Dangerous - user input directly in command
const filename = userInput;
await sandbox.exec(`cat ${filename}`);
// User could input: "file.txt; rm -rf /"
// Safe - validate input
const filename = userInput.replace(/[^a-zA-Z0-9._-]/g, '');
await sandbox.exec(`cat ${filename}`);
// Better - use file API
await sandbox.writeFile('/tmp/input', userInput);
await sandbox.exec('cat /tmp/input');サンドボックス ID は基本的なアクセス制御になりますが、暗号学的に安全ではありません。アプリケーション層の認証を追加してください。
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const userId = await authenticate(request);
if (!userId) {
return new Response('Unauthorized', { status: 401 });
}
// User can only access their sandbox
const sandbox = getSandbox(env.Sandbox, userId);
return Response.json({ authorized: true });
}
};プレビュー URL にはランダム生成のトークンが含まれます。URL を知っている人は、そのサービスにアクセスできます。
アクセスを取り消すには、ポートの公開を解除します。
await sandbox.unexposePort(8080);クイックトンネル(sandbox.tunnels.get(port))は、Cloudflare が割り当てるランダムなホスト名の *.trycloudflare.com URL を返します。別のアクセストークンはありません。ホスト名そのものがアクセス制御です。URL を知っている人は、そのサービスに到達できます。アクセスを取り消すには、トンネルを破棄します。
await sandbox.tunnels.destroy(8080);URL はコンテナ再起動後に残りません。再起動すると、実質的にホスト名が入れ替わります。プレビュー URL と同様に、機密性の高いサービスにはアプリケーション層の認証を追加してください。詳細は Tunnels API を参照してください。
from flask import Flask, request, abort
import os
app = Flask(__name__)
def check_auth():
token = request.headers.get('Authorization')
if token != f"Bearer {os.environ['AUTH_TOKEN']}":
abort(401)
@app.route('/api/data')
def get_data():
check_auth()
return {'data': 'protected'}サンドボックスプロセスが直接使う値には、ハードコードしたシークレットではなく環境変数を使います。
// Bad - hardcoded in file
await sandbox.writeFile('/workspace/config.js', `
const API_KEY = 'sk_live_abc123';
`);
// Good - use environment variables for values the sandbox process needs
await sandbox.startProcess('node app.js', {
env: {
API_KEY: env.API_KEY, // From Worker environment binding
}
});サンドボックスが直接読む必要のない外部 API の認証情報は、Worker 側に置き、アウトバウンドハンドラーで注入します。
一時的な機密データは片付けます。
try {
await sandbox.writeFile('/tmp/sensitive.txt', secretData);
await sandbox.exec('python process.py /tmp/sensitive.txt');
} finally {
await sandbox.deleteFile('/tmp/sensitive.txt');
}外部 API の認証情報を、環境変数やファイルでサンドボックスへ直接渡すと、サンドボックスプロセスが生きた認証情報を持ちます。内部で動くコードは、その値を読めます。アウトバウンドハンドラーは、認証情報を Worker 側に置き、送信リクエストへ注入することで、この露出をなくします。
流れは次のとおりです。
Sandbox request → Outbound handler (injects real credentials) → External APIサンドボックスは本物の認証情報を見ません。Worker の環境でシークレットをローテーションすると、以降のリクエストは更新後の値を使います。
このパターンは、プライベートリポジトリ操作用の GitHub、AI サービス、オブジェクトストレージなど、認証情報をコンテナの外に置きたいときに有効です。実装の詳細は アウトバウンドトラフィックを処理する を参照してください。
- サンドボックス間のアクセス(VM 分離)
- リソース枯渇(適用されるクォータ)
- コンテナエスケープ(VM ベースの分離)
- 認証と認可
- 入力の検証とサニタイズ
- レート制限
- アプリケーション層のセキュリティ(SQL インジェクション、XSS など)
分離には別のサンドボックスを使う:
const sandbox = getSandbox(env.Sandbox, `user-${userId}`);入力はすべて検証する:
const safe = input.replace(/[^a-zA-Z0-9._-]/g, '');
await sandbox.exec(`command ${safe}`);シークレットには環境変数を使う:
await sandbox.startProcess('node app.js', {
env: { API_KEY: env.API_KEY }
});一時リソースを片付ける:
try {
const sandbox = getSandbox(env.Sandbox, sessionId);
await sandbox.exec('npm test');
} finally {
await sandbox.destroy();
}- Containers のアーキテクチャ - 基盤プラットフォームのセキュリティ
- サンドボックスのライフサイクル - リソース管理