mech.app
Security

BoxAgnts WebAssembly Sandbox: Isolating Agent File, Shell, and Network Access

How BoxAgnts uses WASM capability-based security to sandbox agents with file I/O, shell execution, and network access without VM overhead.

Source: dev.to
BoxAgnts WebAssembly Sandbox: Isolating Agent File, Shell, and Network Access

When you give an agent file write, shell execution, and network access, you hand it the keys to your infrastructure. BoxAgnts addresses this by running agent code inside a WebAssembly sandbox, using capability-based security to enforce boundaries without the overhead of containers or VMs.

This is not a wrapper around Docker. It is instruction-level isolation that verifies every memory access at runtime, embedded as a library rather than orchestrated through a daemon.

Why WASM Instead of Containers

BoxAgnts uses Wasmtime, a production-grade WASM runtime from the Bytecode Alliance. The choice is deliberate: containers isolate at the process level, VMs at the hardware level, but WASM isolates at the instruction level with hardware-assisted bounds checking.

DimensionDocker ContainerVMWebAssembly (Wasmtime)
StartupSecondsMinutesMilliseconds
Memory overhead~50MB+~500MB+~1MB
Isolation granularityProcess-levelHardware-levelInstruction-level
Cross-platformYesNoYes
Near-native performanceYesNoYes (Cranelift JIT)
EmbeddabilityRequires daemonRequires hypervisorLibrary-level

The instruction-level verification happens during execution, not as a post-hoc audit. Every memory access is checked against sandbox boundaries before it completes. This is enforced by the WASM runtime, not by kernel namespaces or cgroups.

The RunOption Control Surface

BoxAgnts exposes an 11-dimensional configuration struct that maps host resources into the guest environment. This is not a binary on/off switch. It is a capability grant system.

pub struct RunOption {
    pub work_dir: Option<String>,           // Host dir → guest root mapping
    pub map_dirs: Option<Vec<(String, String)>>, // Multi-directory mapping
    pub env_vars: Option<Vec<(String, Option<String>)>>, // Environment injection
    pub inherit_env: bool,                  // Inherit host environment
    pub inherit_stdio: bool,                // Stdio passthrough
    pub inherit_args: bool,                 // Argument passthrough
    pub allow_network: bool,                // Network capability grant
    pub allow_http: bool,                   // HTTP-specific grant
    pub allow_sockets: bool,                // Raw socket grant
    pub allow_ip_name_lookup: bool,         // DNS resolution grant
    pub allow_read_write: bool,             // Filesystem write grant
}

Each field is a capability. Setting allow_network: false does not just block network calls. It removes the network capability from the WASM module’s import table. The agent code cannot call network functions because they do not exist in its runtime environment.

Filesystem Mapping and Preopens

WASM has no concept of a global filesystem. BoxAgnts uses WASI preopens to grant access to specific directories. The work_dir field maps a host directory to the guest’s root. The map_dirs field allows multiple directory mappings.

let mut builder = WasiCtxBuilder::new();
if let Some(work_dir) = &run_option.work_dir {
    builder = builder.preopened_dir(
        Dir::open_ambient_dir(work_dir, ambient_authority())?,
        "/"
    )?;
}

The agent sees / as its root, but it is actually a subdirectory on the host. Attempts to access ../ or /etc/passwd fail because the WASM runtime enforces the preopen boundary. There is no path traversal outside the granted directory.

If allow_read_write: false, the directory is mounted read-only. The agent can list files and read content, but write calls return permission errors.

Shell Execution Inside the Sandbox

Agents often need to run shell commands. BoxAgnts does not give them a real shell. It spawns processes inside the WASM sandbox using WASI’s process model.

When an agent calls execute_shell("ls -la"), BoxAgnts:

  1. Parses the command into a WASI-compatible process descriptor
  2. Spawns the process inside the sandbox with inherited stdio
  3. Captures stdout/stderr and exit codes
  4. Returns the result to the agent

The spawned process inherits the same capability restrictions as the parent WASM module. If the agent does not have network access, neither does the subprocess. If the filesystem is read-only, the subprocess cannot write files.

This is different from running docker exec. There is no container runtime. The process runs in the same WASM instance, subject to the same instruction-level isolation.

Network Capabilities and Socket Grants

Network access is split into three capabilities:

  • allow_network: General network operations
  • allow_http: HTTP client requests
  • allow_sockets: Raw socket access

Setting allow_http: true but allow_sockets: false lets the agent make HTTP requests but blocks raw TCP/UDP socket creation. This is useful for agents that need to call APIs but should not open arbitrary network connections.

DNS resolution is controlled separately via allow_ip_name_lookup. An agent can have socket access but no DNS, forcing it to use IP addresses directly. This limits its ability to reach arbitrary domains.

The network stack is implemented via WASI sockets, which are capability-based. The agent receives a socket handle only if the capability is granted. There is no way to bypass this by calling libc functions directly because libc is not available in the WASM environment.

Performance Overhead

WASM is not free. The Cranelift JIT compiler adds latency to the first execution while it compiles WASM bytecode to native machine code. Subsequent executions run at near-native speed.

For typical agent workloads (file I/O, HTTP calls, subprocess spawning), the overhead is:

  • First execution: 10-50ms compile time + execution
  • Subsequent executions: <1ms overhead vs. native
  • Memory footprint: ~1MB baseline + agent code size

This is acceptable for agents that run multiple operations per invocation. It is less suitable for single-shot, latency-critical tasks where the compile overhead dominates.

Escape Attempts and Violation Logging

When an agent tries to access a resource outside its capabilities, the WASM runtime traps. This is not a soft error. The entire WASM instance terminates.

BoxAgnts catches these traps and logs them:

match instance.call(&mut store, "main", &[]) {
    Ok(_) => Ok(()),
    Err(e) => {
        log::error!("Sandbox violation: {}", e);
        Err(SandboxError::CapabilityViolation(e.to_string()))
    }
}

The log includes the trap reason (out-of-bounds memory access, missing import, etc.) and the instruction pointer. This is useful for debugging but also for detecting malicious behavior.

Repeated violations from the same agent code suggest either a bug or an escape attempt. BoxAgnts does not currently implement rate limiting or banning, but the logging infrastructure supports it.

State Persistence Across Invocations

WASM instances are ephemeral. When the agent finishes execution, the instance is destroyed and all memory is freed. This is a problem for agents that need durable state.

BoxAgnts solves this by mapping a host directory into the sandbox and instructing agents to write state files there. The agent reads its state from disk on startup and writes it back before termination.

This is not automatic. The agent must implement state serialization. BoxAgnts provides the filesystem boundary but does not manage state lifecycle.

For agents that need in-memory state across invocations, BoxAgnts supports WASM module snapshotting (via Wasmtime’s snapshot API), but this is experimental and not recommended for production.

POSIX Semantics and Compatibility Gaps

WASM does not support full POSIX semantics. Agents that expect fork(), signals, or raw sockets will fail.

BoxAgnts does not attempt to emulate these. Instead, it provides a compatibility layer for common operations:

  • Process spawning via WASI process model (no fork())
  • Signal handling via WASI poll (no SIGTERM)
  • Network I/O via WASI sockets (no raw sockets unless explicitly granted)

Agents that rely on POSIX-specific behavior must be rewritten or run outside the sandbox. This is a trade-off: you gain security but lose compatibility with legacy code.

Technical Verdict

Use BoxAgnts’ WASM sandbox when:

  • You need sub-second startup for agent invocations
  • You want instruction-level isolation without VM overhead
  • You can rewrite or adapt agent code to WASI semantics
  • You need fine-grained capability control (read-only filesystem, HTTP-only network, etc.)

Avoid it when:

  • You need full POSIX compatibility (fork, signals, raw sockets)
  • Your agents are single-shot, latency-critical tasks where compile overhead matters
  • You require automatic state persistence without agent cooperation
  • You already have a container orchestration system and do not want to introduce a second isolation layer

The WASM sandbox is lighter than containers but requires more agent code discipline. It is a good fit for multi-tenant agent platforms where startup speed and memory density matter more than POSIX compatibility.

Tags

agentic-ai orchestration infrastructure

Primary Source

dev.to