Cloudflare Sandbox SDK Usage
This skill covers using the Cloudflare Sandbox SDK in cloudx.sh for secure container execution.
Core Concepts
Sandbox Lifecycle
- Get sandbox instance via Durable Object binding
- Execute operations (git clone, file writes, commands)
- Expose ports for user access
- Proxy requests to the running container
Session Management
Each GitHub repository gets a unique session ID (UUID). Sessions are tracked in KV:
// Session cache keys
`session:${owner}/${repo}` → sessionId // 2hr TTL
`info:${sessionId}` → { status, repo, ... } // 2hr TTL
`preview:${sessionId}` → previewUrl // 2hr TTL
`lock:${owner}/${repo}` → lockValue // 30s TTL (race prevention)
SDK Patterns
Getting a Sandbox Instance
import { Sandbox, getSandbox, proxyToSandbox } from '@cloudflare/sandbox';
// Re-export for Durable Object registration
export { Sandbox };
// Get sandbox for a specific session
const sandbox = getSandbox(env.SANDBOX, sessionId);
Safe Git Operations
Always use gitCheckout() instead of shell exec for cloning:
// GOOD - Uses SDK method, prevents command injection
await sandbox.gitCheckout(repoUrl, {
targetDir: '/home/user/repo',
depth: 1, // Shallow clone for speed
});
// BAD - Vulnerable to command injection
await sandbox.exec(`git clone ${repoUrl}`); // NEVER DO THIS
File Operations
// Write configuration files
await sandbox.writeFile('/home/user/repo/.opencode.json', JSON.stringify({
provider: { anthropic: { apiKey: env.ANTHROPIC_API_KEY } },
model: { provider: 'anthropic', model: 'claude-opus-4-5-20250514' },
}, null, 2));
// Read files
const content = await sandbox.readFile('/path/to/file');
Command Execution
// Run commands with timeout
await sandbox.exec('npm install', { timeout: 60000 });
// Background processes (for servers)
await sandbox.exec(
'cd /home/user/repo && nohup opencode serve --port 4096 > /tmp/opencode.log 2>&1 &',
{ timeout: 30000 }
);
Port Exposure
// Expose a port for external access
const portInfo = await sandbox.exposePort(4096);
console.log(portInfo.url); // Preview URL for the service
Proxying Requests
// In fetch handler, proxy matching requests to sandbox
const proxyResponse = await proxyToSandbox(request, env);
if (proxyResponse) {
return proxyResponse;
}
Security Considerations
Input Validation
Always validate user inputs before using them:
// GitHub owner validation
const GITHUB_OWNER_REGEX = /^[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,37}[a-zA-Z0-9])?$/;
function isValidGitHubOwner(owner: string): boolean {
return GITHUB_OWNER_REGEX.test(owner) && !owner.includes('--');
}
// GitHub repo validation
const GITHUB_REPO_REGEX = /^[a-zA-Z0-9._-]{1,100}$/;
function isValidGitHubRepo(repo: string): boolean {
return GITHUB_REPO_REGEX.test(repo) && repo !== '.' && repo !== '..';
}
// Session ID validation (UUID format)
const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
function isValidSessionId(sessionId: string): boolean {
return UUID_REGEX.test(sessionId);
}
Sanitization for Shell
When shell execution is unavoidable:
function sanitizeForShell(input: string): string {
return input.replace(/[^a-zA-Z0-9._-]/g, '');
}
Race Condition Prevention
Use KV-based locking to prevent duplicate sessions:
// Try to acquire lock
const lockValue = crypto.randomUUID();
const existingLock = await env.CACHE.get(lockKey);
if (existingLock) {
// Another request is creating session, wait and retry
await new Promise(resolve => setTimeout(resolve, 1000));
// ... check for existing session
}
// Set lock with short TTL
await env.CACHE.put(lockKey, lockValue, { expirationTtl: 30 });
try {
// Create session...
} finally {
await env.CACHE.delete(lockKey);
}
Container Configuration
Wrangler Config
{
"containers": [{
"class_name": "Sandbox",
"image": "./Dockerfile",
"max_instances": 10
}],
"durable_objects": {
"bindings": [{
"name": "SANDBOX",
"class_name": "Sandbox"
}]
},
"migrations": [{
"tag": "v1",
"new_sqlite_classes": ["Sandbox"]
}]
}
Env Interface
interface Env {
SANDBOX: DurableObjectNamespace<Sandbox>;
CACHE: KVNamespace;
ANTHROPIC_API_KEY: string;
ENVIRONMENT: string;
}
Status Tracking
Track session status through initialization:
type SessionStatus = 'initializing' | 'cloning' | 'starting' | 'running' | 'error';
async function updateSessionStatus(
env: Env,
sessionId: string,
status: SessionStatus,
error?: string
): Promise<void> {
const infoKey = `info:${sessionId}`;
const existing = await env.CACHE.get(infoKey, 'json');
if (existing) {
await env.CACHE.put(infoKey, JSON.stringify({
...existing,
status,
error,
updatedAt: Date.now(),
}), { expirationTtl: 7200 });
}
}
Troubleshooting
Container Not Enabled
Check that containers[].class_name matches durable_objects.bindings[].class_name.
Image Registry Error
Use ./Dockerfile for image path. Cloudflare builds and pushes to their registry.
Port Exposure Fails
Ensure the container exposes the port in Dockerfile (EXPOSE 4096).
Session Not Found
Sessions expire after 2 hours. Check KV for info:{sessionId} to see status.
