ai-sandbox 0.1.5

Cross-platform AI tool sandbox security implementation
Documentation

AI Sandbox - Cross-platform AI Tool Sandbox Security

A Rust crate providing cross-platform sandbox isolation for AI agent tools.

Features

  • Multi-platform sandboxing:

    • Linux: Bubblewrap + Seccomp + Landlock
    • macOS: Seatbelt (sandbox-exec)
    • Windows: Restricted Token
    • FreeBSD: Capsicum
    • OpenBSD: pledge
  • Process hardening: Pre-main() security hardening

  • Execution policy engine: Rule-based command execution control

  • Network policy: Fine-grained network access control

  • Filesystem policy: Read-only, workspace-only, or full access

Platform Support

Platform Sandbox Type Status
Linux Bubblewrap/Seccomp/Landlock
macOS Seatbelt (sandbox-exec)
Windows Restricted Token
FreeBSD Capsicum
OpenBSD pledge

Installation

# Cargo.toml
[dependencies]
ai-sandbox = "0.1.5"

Testing

# Run the test script
./test.sh

# Or run manually:
# Check compilation
cargo check

# Run all tests
cargo test

# Run demo example
cargo run --example demo

# Build release version
cargo build --release

Creating Your Own Sandbox Application

Step 1: Add Dependency

# Cargo.toml
[dependencies]
ai-sandbox = "0.1.5"

Step 2: Basic Usage Example

Create a file examples/sandbox_app.rs:

use ai_sandbox::{
    SandboxManager, 
    SandboxPolicy, 
    SandboxCommand,
    FileSystemSandboxPolicy,
    NetworkSandboxPolicy,
    get_platform_sandbox,
};
use std::ffi::OsString;
use std::collections::HashMap;
use std::path::PathBuf;
use std::process::{Command, Stdio};

fn main() {
    // Check platform support
    let sandbox_type = get_platform_sandbox(false);
    println!("Using sandbox type: {:?}", sandbox_type);
    
    // Create sandbox manager
    let manager = SandboxManager::new();
    
    // Define your policy
    let policy = SandboxPolicy::ReadOnly {
        file_system: FileSystemSandboxPolicy::ReadOnly,
        network_access: NetworkSandboxPolicy::NoAccess,
    };
    
    // Create command to sandbox
    let command = SandboxCommand {
        program: OsString::from("ls"),
        args: vec!["-la".to_string(), "/etc".to_string()],
        cwd: PathBuf::from("/tmp"),
        env: HashMap::new(),
    };
    
    // Transform command for sandbox execution
    match manager.create_exec_request(command, policy) {
        Ok(request) => {
            println!("Sandbox policy applied: {:?}", request.sandbox_policy);
            println!("Command: {:?}", request.command);
            
            // Execute the sandboxed command
            // In production, use request.command with proper execution
        }
        Err(e) => {
            eprintln!("Sandbox error: {}", e);
        }
    }
}

Step 3: Run Your Example

cargo run --example sandbox_app

Advanced Usage

Custom Sandbox Policy

use ai_sandbox::{SandboxPolicy, NetworkSandboxPolicy};
use std::path::PathBuf;

// Only allow writing to specific directories
let policy = SandboxPolicy::WorkspaceWrite {
    writable_roots: vec![
        PathBuf::from("/tmp"),
        PathBuf::from("/home/user/workspace"),
    ],
    network_access: NetworkSandboxPolicy::Localhost,  // Only localhost
};

Execution Policy (Command Allow/Deny)

use ai_sandbox::{Policy, Decision};

let mut exec_policy = Policy::new();

// Deny dangerous commands
exec_policy.add_prefix_rule(
    &["rm".to_string(), "-rf".to_string()],
    Decision::Deny,
    Some("Deleting files recursively is not allowed".to_string()),
).unwrap();

// Deny network downloads
exec_policy.add_prefix_rule(
    &["curl".to_string()],
    Decision::Deny,
    Some("Network downloads are restricted".to_string()),
).unwrap();

exec_policy.add_prefix_rule(
    &["wget".to_string()],
    Decision::Deny,
    Some("Network downloads are restricted".to_string()),
).unwrap();

// Allow read-only commands
exec_policy.add_prefix_rule(
    &["ls".to_string()],
    Decision::Allow,
    None,
).unwrap();

exec_policy.add_prefix_rule(
    &["cat".to_string()],
    Decision::Allow,
    None,
).unwrap();

// Check a command
let result = exec_policy.check(&["rm".to_string(), "-rf".to_string(), "/".to_string()]);
match result {
    Some(m) => {
        if m.decision == Decision::Deny {
            println!("Command denied: {}", m.justification.as_deref().unwrap_or("No reason"));
        }
    }
    None => println!("Command allowed (no matching rule)"),
}

Process Hardening

use ai_sandbox::process_hardening::pre_main_hardening;

// Call at program startup (before main)
fn init() {
    pre_main_hardening();
}

// Or use constructor crate for automatic calling
// Add to Cargo.toml: ctor = "0.2"
#[cfg_attr(not(test), ctor::ctor)]
fn init() {
    pre_main_hardening();
}

Platform-Specific Features

Linux with Bubblewrap

use ai_sandbox::linux_sandbox::{find_system_bwrap_in_path, system_bwrap_warning};

// Check if bubblewrap is available
if let Some(warning) = system_bwrap_warning() {
    eprintln!("Warning: {}", warning);
}

// Get bwrap path
let bwrap_path = find_system_bwrap_in_path();

Linux with Landlock

use ai_sandbox::linux_sandbox::landlock::{is_landlock_available, get_landlock_version};

// Check Landlock support
if is_landlock_available() {
    let version = get_landlock_version().unwrap_or(0);
    println!("Landlock version: {}", version);
}

macOS Seatbelt

use ai_sandbox::sandboxing::seatbelt::{create_seatbelt_policy, proxy_loopback_ports_from_env};
use std::collections::HashMap;

// Create Seatbelt policy string
let policy = create_seatbelt_policy(&sandbox_policy);
println!("Seatbelt policy:\n{}", policy);

// Get proxy ports from environment
let mut env = HashMap::new();
env.insert("HTTP_PROXY".to_string(), "http://localhost:8080".to_string());
let ports = proxy_loopback_ports_from_env(&env);

Complete AI Agent Example

use ai_sandbox::{
    SandboxManager, SandboxPolicy, SandboxCommand, SandboxType,
    FileSystemSandboxPolicy, NetworkSandboxPolicy, Policy, Decision,
};
use std::collections::HashMap;
use std::ffi::OsString;
use std::path::PathBuf;
use std::process::Command;

struct AgentSandbox {
    manager: SandboxManager,
    exec_policy: Policy,
}

impl AgentSandbox {
    fn new() -> Self {
        let mut exec_policy = Policy::new();
        
        // Configure execution policy
        exec_policy.add_prefix_rule(&["rm".to_string()], Decision::Deny, None).unwrap();
        exec_policy.add_prefix_rule(&["mkfs".to_string()], Decision::Deny, None).unwrap();
        exec_policy.add_prefix_rule(&["dd".to_string()], Decision::Deny, None).unwrap();
        
        Self {
            manager: SandboxManager::new(),
            exec_policy,
        }
    }
    
    fn execute(&self, program: &str, args: Vec<String>, cwd: PathBuf) -> Result<(), String> {
        let command = SandboxCommand {
            program: OsString::from(program),
            args,
            cwd: cwd.clone(),
            env: HashMap::new(),
        };
        
        // Check execution policy first
        let mut full_command = vec![program.to_string()];
        full_command.extend(command.args.clone());
        
        if let Some(m) = self.exec_policy.check(&full_command) {
            if m.decision == Decision::Deny {
                return Err(format!("Command denied: {:?}", m.justification));
            }
        }
        
        // Create sandboxed request
        let policy = SandboxPolicy::WorkspaceWrite {
            writable_roots: vec![cwd],
            network_access: NetworkSandboxPolicy::Localhost,
        };
        
        let request = self.manager
            .create_exec_request(command, policy)
            .map_err(|e| e.to_string())?;
        
        println!("Executing: {:?}", request.command);
        Ok(())
    }
}

fn main() {
    let sandbox = AgentSandbox::new();
    
    // This should be allowed
    sandbox.execute("ls", vec!["-la".to_string()], PathBuf::from("/tmp")).unwrap();
    
    // This should be denied by policy
    let result = sandbox.execute("rm", vec!["-rf".to_string(), "/".to_string()], PathBuf::from("/tmp"));
    assert!(result.is_err());
}

Platform-Specific Notes

Linux

Requires bubblewrap (bwrap) installed on the system. On Ubuntu/Debian:

apt install bubblewrap

macOS

Uses the native sandbox-exec command (available in /usr/bin/sandbox-exec).

Windows

Uses Windows Restricted Token API. Requires Windows 10 version 1709 or later.

FreeBSD

Uses Capsicum framework for capability-mode sandboxing.

OpenBSD

Uses the pledge() system call for system call filtering.

License

MIT