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:
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:
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.
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:
// 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:
// 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():
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():
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:
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:
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:
// 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:
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:
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
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
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:
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.
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:
- The host directory
./workspaceis exposed via virtiofs - The sandbox init automatically mounts it at
/mnt/workspace - 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 filesMountMode::ReadOnly- Guest can only read files
Complete Example
Here is a complete example demonstrating sandbox mode for executing Python code:
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:
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:
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:
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
| Feature | Raw VM | Sandbox |
|---|---|---|
| Custom kernel/initrd | Required | Optional override |
| Directory mounting | Manual | Automatic |
| Command execution | Console scraping | Structured RPC |
| File operations | Via console or virtiofs | Direct API |
| Environment | Unknown | Guaranteed |
| Setup complexity | High | Low |
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.