Agent skill
sandbox-usage
Use when working with the Cloudflare Sandbox SDK, container lifecycle, session management, or OpenCode integration. Covers SDK patterns, security considerations, and common operations.
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/development/sandbox-usage
SKILL.md
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.
Didn't find tool you were looking for?