Skip to content

Isolation Model

Runra Sandbox provides VM-backed isolation for every sandbox. This means each sandbox runs in its own virtual machine, not just a container — giving you stronger security boundaries than container-based solutions.

Each sandbox runs inside a dedicated VM provisioned by CubeSandbox, the open-source engine that powers Runra. This provides:

  • Kernel-level isolation: Separate kernel instances prevent privilege escalation between sandboxes
  • No shared OS state: /proc, /sys, and other kernel interfaces are sandbox-private
  • Guaranteed resource allocation: CPU cores and memory pages are assigned per VM, not shared
┌─────────────────────────────────────────────┐
│ Host Node │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Sandbox A│ │ Sandbox B│ │ Sandbox C│ │
│ │ VM #1 │ │ VM #2 │ │ VM #3 │ │
│ │ CPU: 2 │ │ CPU: 4 │ │ CPU: 8 │ │
│ │ RAM: 4GB │ │ RAM: 8GB │ │ RAM:16GB │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ ↕ ↕ ↕ │
│ ┌──────────────────────────────────────┐ │
│ │ CubeProxy (network layer) │ │
│ └──────────────────────────────────────┘ │
└─────────────────────────────────────────────┘

Every sandbox gets its own private filesystem workspace. There is no shared filesystem between sandboxes:

const sandboxA = await runra.sandboxes.create({ image: "node:22" });
const sandboxB = await runra.sandboxes.create({ image: "node:22" });
await sandboxA.files.write("/workspace/secret.txt", "token-abc");
await sandboxB.files.write("/workspace/secret.txt", "token-xyz");
// Each sandbox only sees its own file
const a = await sandboxA.files.read("/workspace/secret.txt"); // "token-abc"
const b = await sandboxB.files.read("/workspace/secret.txt"); // "token-xyz"
/workspace/ ← Your sandbox workspace (persisted across pause/resume)
├── app/ ← Your application code
├── node_modules/ ← Dependencies (preserved on pause)
└── ...
/tmp/ ← Ephemeral temp (cleared on sandbox restart)

All standard file operations go through the sandbox API:

// Write a file
await sandbox.files.write("/workspace/app.ts", content);
// Read a file
const content = await sandbox.files.read("/workspace/app.ts");
// List directory
const files = await sandbox.files.list("/workspace");
// Check if exists
const exists = await sandbox.files.exists("/workspace/app.ts");
// Delete a file
await sandbox.files.delete("/workspace/temp.log");
// Create directory
await sandbox.files.mkdir("/workspace/src/components");

Processes in one sandbox cannot see or interact with processes in another:

// Sandbox A runs a server
await sandboxA.exec("python -m http.server 8000 &");
// Sandbox B cannot see the server process
const result = await sandboxB.exec("ps aux");
// Shows only sandbox B's processes — no http.server from sandbox A

Each sandbox has its own PID namespace, mount namespace, and IPC namespace — standard Linux namespace isolation reinforced by the VM boundary.

Every sandbox has hard resource limits that cannot be exceeded:

ResourceDefaultMaxDescription
CPU cores28Dedicated vCPUs
Memory4096 MB16384 MBHard limit, OOM kill on exceed
Disk10240 MB51200 MBWorkspace storage
Processes2561024Max simultaneous processes
const sandbox = await runra.sandboxes.create({
image: "node:22",
resources: {
cpu: 4, // 4 dedicated vCPUs
memoryMb: 8192, // 8 GB RAM (hard limit)
diskMb: 20480, // 20 GB disk
},
});

If a sandbox exceeds its memory limit, the kernel OOM killer terminates the offending process. If CPU usage is sustained at 100%, the VM is throttled to its allocated cores — no noisy neighbor problems.

Network access is controlled per sandbox:

const sandbox = await runra.sandboxes.create({
image: "node:22",
network: {
// Allow outbound to specific hosts
egress: {
allow: ["github.com", "registry.npmjs.org", "api.openai.com"],
// Block everything else
default: "deny",
},
// Inbound is only through exposed ports
ingress: {
allowPorts: [3000, 8080],
},
},
});
DirectionDefaultDescription
Outbound (egress)Allow allSandbox can reach any internet host
Inbound (ingress)Deny allOnly ports explicitly exposed are accessible
Sandbox-to-sandboxDenySandboxes cannot communicate with each other

Only explicitly exposed ports are reachable from the internet:

// Expose port 3000 — generates a public URL
const port = await sandbox.exposePort(3000);
console.log(port.url); // https://3000-abc123.box.runra.dev

See Exposing Ports for the full port exposure guide.

Prevent runaway sandboxes with automatic lifecycle controls:

const sandbox = await runra.sandboxes.create({
image: "node:22",
timeoutMs: 300_000, // Auto-terminate after 5 minutes (hard limit)
idleTimeoutMs: 120_000, // Auto-pause after 2 minutes of inactivity
});
TimeoutTriggerAction
timeoutMsTotal sandbox lifetime exceededForce terminate
idleTimeoutMsNo exec calls for specified durationAuto-pause
Per-command timeoutIndividual exec() takes too longAbort command, sandbox stays running
// Per-command timeout
const result = await sandbox.exec("npm run long-build", {
timeoutMs: 60_000, // Kill if build takes more than 60 seconds
});

Every sandbox is disposable by design. Terminate cleans up all resources immediately:

await sandbox.terminate();
// VM destroyed, disk wiped, ports released, IP recycled

No data persists after termination. If you need state to survive, use pause instead.

Runra Sandbox provides these security guarantees:

  1. No cross-sandbox access: Sandbox A cannot read Sandbox B’s files, processes, or network traffic
  2. No host escape: VM isolation prevents escape to the host node
  3. No resource stealing: Hard resource limits prevent one sandbox from starving others
  4. No persistent side effects: Termination wipes everything
  5. Network control: You control what each sandbox can reach