Skip to content

Sandbox Mode

Sandbox mode provides an opinionated VM configuration designed for secure, isolated code execution. Unlike raw VMs where you bring your own kernel and must handle mounting and communication manually, the sandbox comes with batteries included: a capsa-controlled kernel, automatic directory mounting, and a built-in guest agent for structured interaction.

What is Sandbox Mode?

A sandbox is a VM with guaranteed features:

  • Auto-mounting: Shared directories are automatically mounted at the specified guest paths (Linux only)
  • Guest agent: A built-in RPC agent enables structured command execution and file operations
  • No console scraping: Proper request/response protocol instead of parsing terminal output
  • Known environment: Predictable kernel version, modules, and capabilities

This makes sandbox mode ideal for:

  • AI coding agents that need to execute untrusted code
  • Automated testing in isolated environments
  • Build systems requiring reproducible, sandboxed execution
  • Any workload where you need structured interaction with a VM

Creating a Sandbox

Use Capsa::sandbox() to start building a sandbox VM:

rust
use capsa::{Capsa, sandbox, MountMode};
use std::collections::HashMap;

let vm = Capsa::sandbox()
    .run("/bin/sh", &["-c", "sleep infinity"])
    .build()
    .await?;

Specifying the Main Process

Every sandbox must specify a main process. The builder enforces this at compile time using the typestate pattern.

Run a binary:

rust
let vm = Capsa::sandbox()
    .run("/bin/sh", &["-c", "sleep infinity"])
    .build()
    .await?;

Run an OCI container (planned):

Not Yet Implemented

OCI container support is planned for a future release. Use .run() with a binary for now.

rust
let vm = Capsa::sandbox()
    .oci("python:3.11", &["python", "-c", "import time; time.sleep(3600)"])
    .build()
    .await?;

Compile-Time Safety

You cannot call .build() without first calling .run() or .oci(). The compiler will reject code that attempts to build a sandbox without a main process:

rust
// This will not compile
Capsa::sandbox()
    .cpus(2)
    .build()  // Error: method `build` not found
    .await?;

Mutual Exclusion

The .run() and .oci() methods are mutually exclusive. Once you call one, the other is no longer available:

rust
// This will not compile
Capsa::sandbox()
    .run("/bin/sh", &[])
    .oci("python:3.11", &[])  // Error: method `oci` not found
    .build()
    .await?;

Connecting to the Agent

The guest agent runs automatically inside the sandbox. After the VM boots, connect to it using sandbox::wait_ready():

rust
use capsa::{Capsa, sandbox};

let vm = Capsa::sandbox()
    .run("/bin/sh", &["-c", "sleep infinity"])
    .build()
    .await?;

// Get the vsock socket for the agent port
let socket = vm.vsock_socket(sandbox::agent_port()).unwrap();

// Wait for the agent to become ready (default timeout: 30 seconds)
let agent = sandbox::wait_ready(&socket).await?;

For custom timeouts, use sandbox::wait_ready_timeout():

rust
use std::time::Duration;

let agent = sandbox::wait_ready_timeout(&socket, Duration::from_secs(60)).await?;

Agent Operations

Once connected, the agent provides structured access to the guest environment.

Command Execution

Execute shell commands and receive structured output:

rust
use std::collections::HashMap;

let result = agent.exec("ls -la /mnt", HashMap::new()).await?;
println!("stdout: {}", result.stdout);
println!("stderr: {}", result.stderr);
println!("exit code: {}", result.exit_code);

Pass environment variables as the second argument:

rust
let mut env = HashMap::new();
env.insert("MY_VAR".to_string(), "my_value".to_string());

let result = agent.exec("echo $MY_VAR", env).await?;
assert_eq!(result.stdout.trim(), "my_value");

File Operations

Read and write files in the guest filesystem:

rust
// Write a file
agent.write_file("/tmp/input.txt", b"hello world").await?;

// Read a file
let contents = agent.read_file("/tmp/output.txt").await?;
println!("Contents: {}", String::from_utf8_lossy(&contents));

// Check if a path exists
let exists = agent.exists("/tmp/input.txt").await?;
assert!(exists);

// List directory contents
let entries = agent.list_dir("/tmp").await?;
for entry in entries {
    println!("{}: {:?}", entry.name, entry.file_type);
}

System Information

Query information about the guest system:

rust
let info = agent.info().await?;
println!("Kernel: {}", info.kernel_version);
println!("Hostname: {}", info.hostname);
println!("CPUs: {}", info.cpus);
println!("Memory: {} MB", info.memory_mb);

Shutdown

Request a clean shutdown of the VM:

rust
agent.shutdown().await?;

This signals the init process to terminate the main process gracefully before shutting down the VM.

Configuration Options

The sandbox builder supports various configuration options:

Resources

rust
let vm = Capsa::sandbox()
    .cpus(4)           // Number of virtual CPUs
    .memory_mb(2048)   // Memory in megabytes
    .run("/bin/sh", &["-c", "sleep infinity"])
    .build()
    .await?;

Networking

rust
use capsa::NetworkMode;

let vm = Capsa::sandbox()
    .network(NetworkMode::UserNat(Default::default()))
    .run("/bin/sh", &["-c", "sleep infinity"])
    .build()
    .await?;

// Or disable networking entirely
let vm = Capsa::sandbox()
    .no_network()
    .run("/bin/sh", &["-c", "sleep infinity"])
    .build()
    .await?;

Custom Kernel and Initrd

For development or testing, you can override the default sandbox kernel and initrd:

rust
let vm = Capsa::sandbox()
    .kernel("/path/to/custom/kernel")
    .initrd("/path/to/custom/initrd")
    .run("/bin/sh", &["-c", "sleep infinity"])
    .build()
    .await?;

Shared Directories

One of the key advantages of sandbox mode is automatic directory mounting. Unlike raw VMs where you must manually mount virtiofs shares, the sandbox handles this for you.

rust
use capsa::MountMode;

let vm = Capsa::sandbox()
    .share("./workspace", "/mnt/workspace", MountMode::ReadWrite)
    .share("./data", "/mnt/data", MountMode::ReadOnly)
    .run("/mnt/workspace/run.sh", &[])
    .build()
    .await?;

When the sandbox boots:

  1. The host directory ./workspace is exposed via virtiofs
  2. The sandbox init automatically mounts it at /mnt/workspace
  3. Your main process can immediately access files at that path

Difference from Raw VMs

With raw VMs, .share() only attaches a virtiofs device. You must manually mount it inside the guest. With sandbox mode, the guest path you specify is the actual mount point.

Mount Modes

  • MountMode::ReadWrite - Guest can read and write files
  • MountMode::ReadOnly - Guest can only read files

Complete Example

Here is a complete example demonstrating sandbox mode for executing Python code:

rust
use capsa::{Capsa, sandbox, MountMode};
use std::collections::HashMap;

async fn run_python_in_sandbox() -> capsa::Result<()> {
    // Create a sandbox with shared code directory
    let vm = Capsa::sandbox()
        .cpus(2)
        .memory_mb(512)
        .share("./code", "/mnt/code", MountMode::ReadOnly)
        .run("/bin/sh", &["-c", "sleep infinity"])
        .build()
        .await?;

    // Connect to the agent
    let socket = vm.vsock_socket(sandbox::agent_port()).unwrap();
    let agent = sandbox::wait_ready(&socket).await?;

    // Execute Python script
    let result = agent.exec(
        "python3 /mnt/code/script.py",
        HashMap::new()
    ).await?;

    println!("Output: {}", result.stdout);

    if result.exit_code != 0 {
        eprintln!("Script failed: {}", result.stderr);
    }

    // Read any output files
    if agent.exists("/tmp/results.json").await? {
        let data = agent.read_file("/tmp/results.json").await?;
        println!("Results: {}", String::from_utf8_lossy(&data));
    }

    // Clean shutdown
    agent.shutdown().await?;

    Ok(())
}

Use Cases

AI Agent Code Execution

Run untrusted code from AI models safely:

rust
let vm = Capsa::sandbox()
    .cpus(1)
    .memory_mb(256)
    .no_network()  // Disable network for safety
    .share("./generated_code", "/code", MountMode::ReadOnly)
    .share("./output", "/output", MountMode::ReadWrite)
    .run("/bin/sh", &["-c", "sleep infinity"])
    .build()
    .await?;

let agent = sandbox::wait_ready(&socket).await?;

// Execute AI-generated code
let result = agent.exec("/code/solution.py", HashMap::new()).await?;

// Collect results from the output directory
let files = agent.list_dir("/output").await?;

Automated Testing

Run integration tests in isolated environments:

rust
let vm = Capsa::sandbox()
    .cpus(2)
    .memory_mb(1024)
    .share("./project", "/project", MountMode::ReadOnly)
    .run("/bin/sh", &["-c", "sleep infinity"])
    .build()
    .await?;

let agent = sandbox::wait_ready(&socket).await?;

// Run test suite
let result = agent.exec(
    "cd /project && cargo test",
    HashMap::new()
).await?;

assert_eq!(result.exit_code, 0, "Tests failed: {}", result.stderr);

Build Environments

Create reproducible build environments:

rust
let vm = Capsa::sandbox()
    .cpus(4)
    .memory_mb(4096)
    .share("./src", "/src", MountMode::ReadOnly)
    .share("./artifacts", "/artifacts", MountMode::ReadWrite)
    .run("/bin/sh", &["-c", "sleep infinity"])
    .build()
    .await?;

let agent = sandbox::wait_ready(&socket).await?;

// Build the project
let result = agent.exec(
    "cd /src && make && cp -r build/* /artifacts/",
    HashMap::new()
).await?;

Comparison with Raw VMs

FeatureRaw VMSandbox
Custom kernel/initrdRequiredOptional override
Directory mountingManualAutomatic
Command executionConsole scrapingStructured RPC
File operationsVia console or virtiofsDirect API
EnvironmentUnknownGuaranteed
Setup complexityHighLow

Choose sandbox mode when you need structured interaction with the guest. Choose raw VMs when you need complete control over the guest environment or must run a specific OS/kernel.

Released under the MIT License.