Skip to main content
Corvus supports multiple runtime environments through the RuntimeAdapter trait. Each runtime provides different trade-offs between isolation, performance, and platform compatibility.

Runtime Types

Corvus currently supports two production runtimes:

Native Runtime

Direct execution on host OS - maximum performance, minimal isolation

Docker Runtime

Containerized execution - strong isolation, resource limits

Native Runtime

The native runtime executes commands directly on the host operating system with minimal overhead. Location: clients/agent-runtime/src/runtime/native.rs
pub struct NativeRuntime;

impl RuntimeAdapter for NativeRuntime {
    fn name(&self) -> &str {
        "native"
    }

    fn has_shell_access(&self) -> bool {
        true
    }

    fn has_filesystem_access(&self) -> bool {
        true
    }

    fn storage_path(&self) -> PathBuf {
        directories::UserDirs::new().map_or_else(
            || PathBuf::from(".corvus"),
            |u| u.home_dir().join(".corvus"),
        )
    }

    fn supports_long_running(&self) -> bool {
        true
    }

    fn build_shell_command(
        &self,
        command: &str,
        workspace_dir: &Path,
    ) -> anyhow::Result<tokio::process::Command> {
        let mut process = tokio::process::Command::new("sh");
        process.arg("-c").arg(command).current_dir(workspace_dir);
        Ok(process)
    }
}
Use cases:
  • Local development
  • Trusted environments
  • Performance-critical workloads
  • Desktop/CLI applications
Configuration:
[runtime]
kind = "native"
Characteristics:
  • Startup: <100ms
  • Memory overhead: ~20MB
  • Isolation: Policy-based only
  • Platform: Linux, macOS, Windows

Docker Runtime

The Docker runtime executes commands in ephemeral containers for strong isolation. Location: clients/agent-runtime/src/runtime/docker.rs
pub struct DockerRuntime {
    config: DockerRuntimeConfig,
}

impl RuntimeAdapter for DockerRuntime {
    fn name(&self) -> &str {
        "docker"
    }

    fn has_shell_access(&self) -> bool {
        true
    }

    fn has_filesystem_access(&self) -> bool {
        self.config.mount_workspace
    }

    fn storage_path(&self) -> PathBuf {
        if self.config.mount_workspace {
            PathBuf::from("/workspace/.corvus")
        } else {
            PathBuf::from("/tmp/.corvus")
        }
    }

    fn supports_long_running(&self) -> bool {
        false  // Each command is a fresh container
    }

    fn memory_budget(&self) -> u64 {
        self.config.memory_limit_mb
            .map_or(0, |mb| mb.saturating_mul(1024 * 1024))
    }

    fn build_shell_command(
        &self,
        command: &str,
        workspace_dir: &Path,
    ) -> anyhow::Result<tokio::process::Command> {
        let mut process = tokio::process::Command::new("docker");
        process
            .arg("run")
            .arg("--rm")
            .arg("--init")
            .arg("--interactive");

        // Network isolation
        if !self.config.network.is_empty() {
            process.arg("--network").arg(&self.config.network);
        }

        // Memory limit
        if let Some(mb) = self.config.memory_limit_mb.filter(|mb| *mb > 0) {
            process.arg("--memory").arg(format!("{mb}m"));
        }

        // CPU limit
        if let Some(cpus) = self.config.cpu_limit.filter(|cpus| *cpus > 0.0) {
            process.arg("--cpus").arg(cpus.to_string());
        }

        // Read-only root filesystem
        if self.config.read_only_rootfs {
            process.arg("--read-only");
        }

        // Workspace mount
        if self.config.mount_workspace {
            let host_workspace = self.validate_workspace(workspace_dir)?;
            process
                .arg("--volume")
                .arg(format!("{}:/workspace:rw", host_workspace.display()))
                .arg("--workdir")
                .arg("/workspace");
        }

        process
            .arg(&self.config.image)
            .arg("sh")
            .arg("-c")
            .arg(command);

        Ok(process)
    }
}
Use cases:
  • Untrusted code execution
  • Multi-tenant environments
  • Resource-constrained scenarios
  • Production deployments
Configuration:
[runtime]
kind = "docker"

[runtime.docker]
image = "alpine:3.20"              # Base container image
network = "none"                   # Network isolation (none | bridge | host)
memory_limit_mb = 512              # Memory limit in MB
cpu_limit = 2.0                    # CPU cores limit
read_only_rootfs = true            # Read-only filesystem
mount_workspace = true             # Mount workspace directory
allowed_workspace_roots = [        # Allowlist for workspace mounts
  "/home/user/projects",
  "/tmp/workspaces"
]
Characteristics:
  • Startup: ~2-3s (container creation)
  • Memory overhead: ~50MB + container overhead
  • Isolation: Process, network, filesystem
  • Platform: Linux, macOS, Windows (Docker Desktop)

Execution Model

Native Execution Flow

Isolation layers:
  1. Security Policy - Command allowlists, path restrictions
  2. Workspace Scoping - Filesystem access limited to workspace
  3. Landlock/Firejail (optional) - Kernel-level sandboxing

Docker Execution Flow

Isolation layers:
  1. Container Isolation - Separate PID, network, mount namespaces
  2. Resource Limits - Memory, CPU, I/O quotas
  3. Network Isolation - Configurable network access
  4. Filesystem Isolation - Read-only rootfs, selective mounts
  5. Security Policy - Command validation before execution

Sandboxing Approaches

Corvus provides multiple sandboxing mechanisms depending on the runtime and platform: Docker provides the strongest isolation:
Separate PID namespace prevents process visibility and interaction
network=none blocks all network access
Read-only root filesystem with selective mounts
Enforced memory and CPU quotas prevent resource exhaustion
Containers run with minimal Linux capabilities

Linux Kernel Sandboxing

On Linux, native runtime can use kernel-level sandboxing: Landlock (Linux 5.13+):
  • Filesystem access control at kernel level
  • Zero overhead
  • No external dependencies
Firejail:
  • Userspace sandboxing wrapper
  • Namespace isolation
  • Seccomp filtering
Bubblewrap:
  • Lightweight container runtime
  • Used by Flatpak
Detection and selection:
pub fn create_sandbox(policy: &SecurityPolicy) -> Box<dyn Sandbox> {
    // Prefer Landlock on modern kernels
    if landlock::is_supported() {
        return Box::new(LandlockSandbox::new(policy));
    }

    // Fall back to Firejail if available
    if firejail::is_available() {
        return Box::new(FirejailSandbox::new(policy));
    }

    // No-op sandbox as last resort
    Box::new(NoopSandbox)
}

Policy-Based Restrictions

All runtimes enforce policy-based restrictions:
pub struct SecurityPolicy {
    pub autonomy: AutonomyLevel,
    pub workspace_dir: PathBuf,
    pub workspace_only: bool,            // Restrict to workspace
    pub allowed_commands: Vec<String>,    // Command allowlist
    pub forbidden_paths: Vec<String>,     // Path blocklist
    pub max_actions_per_hour: u32,        // Rate limiting
    pub require_approval_for_medium_risk: bool,
    pub block_high_risk_commands: bool,
}
See Security Model for details.

Performance Characteristics

Native Runtime

MetricValue
Cold start<100ms
Command execution overhead<10ms
Memory baseline~20MB
Memory per session~50-100MB
Throughput~100 commands/sec

Docker Runtime

MetricValue
Cold start~2-3s
Command execution overhead~200-500ms
Memory baseline~50MB + image
Memory per session~100-200MB
Throughput~5-10 commands/sec
Optimization tips:
Use native runtime for development and trusted environments
Use Docker runtime for production and untrusted code
Pre-pull Docker images to reduce first-run latency
Use Alpine-based images for minimal overhead

Resource Limits

Memory Limits

Docker:
[runtime.docker]
memory_limit_mb = 512  # Hard limit enforced by Docker
Native: No hard limits - relies on OS scheduling

CPU Limits

Docker:
[runtime.docker]
cpu_limit = 2.0  # Maximum CPU cores
Native: No hard limits - full access to available cores

Timeout Limits

Both runtimes support execution timeouts:
let output = tokio::time::timeout(
    Duration::from_secs(30),
    cmd.output()
).await??;

Workspace Mounting

Native Runtime

Direct filesystem access - no mounting needed. Security:
  • workspace_only policy restricts paths
  • forbidden_paths blocklist
  • Landlock filesystem constraints

Docker Runtime

Selective mounting with validation:
fn validate_workspace(&self, workspace_dir: &Path) -> anyhow::Result<PathBuf> {
    let resolved = workspace_dir.canonicalize()?;

    // Refuse to mount system root
    if resolved == Path::new("/") {
        anyhow::bail!("Refusing to mount filesystem root");
    }

    // Check against allowlist
    if !self.config.allowed_workspace_roots.is_empty() {
        let allowed = self.config.allowed_workspace_roots.iter()
            .any(|root| resolved.starts_with(root));

        if !allowed {
            anyhow::bail!("Workspace not in allowed_workspace_roots");
        }
    }

    Ok(resolved)
}
Mount options:
  • mount_workspace = true - Mount workspace directory
  • allowed_workspace_roots - Allowlist specific paths
  • Read-write access within container

Platform Support

RuntimeLinuxmacOSWindows
Native✅ Full✅ Full✅ Full
Docker✅ Full✅ Full⚠️ Docker Desktop required
Landlock✅ 5.13+
Firejail

Future Runtimes

WASM Runtime (planned) - WebAssembly sandboxing for portable execution
Cloudflare Workers (planned) - Edge runtime support
Kubernetes Runtime (planned) - Cluster-native execution

Best Practices

Do:
  • Use Docker runtime for production deployments
  • Enable read_only_rootfs when possible
  • Set memory and CPU limits
  • Use network isolation (network=none)
  • Validate workspace paths before mounting
  • Pre-pull container images
Don’t:
  • Mount system directories (/etc, /root)
  • Run Docker containers with --privileged
  • Use network=host unless necessary
  • Mount entire home directory
  • Ignore resource limits

Troubleshooting

Docker Runtime Issues

Problem: “Cannot connect to Docker daemon”
# Check Docker is running
docker ps

# Start Docker service
sudo systemctl start docker
Problem: “Permission denied” on workspace mount
# Add workspace to allowlist
[runtime.docker]
allowed_workspace_roots = ["/path/to/workspace"]
Problem: Slow container startup
# Pre-pull the image
docker pull alpine:3.20

Native Runtime Issues

Problem: “Command not found”
# Add command to allowlist
[security]
allowed_commands = ["git", "npm", "cargo", "mycommand"]
Problem: “Path access denied”
# Adjust workspace restrictions
[security]
workspace_only = false
forbidden_paths = []  # Be careful with this

Next Steps

Security Model

Understand security policies and sandboxing

Configuration

Configure runtime settings

Architecture

See how runtimes fit into the system

Deployment Guide

Deploy Corvus to production