When an agent calls a tool, the tool runs code on your machine. That code came indirectly from a language model that read user input. The threat model is obvious; the defence is platform-specific isolation.

openclaw-rs ships three backends behind one Rust API.

The policy abstraction

pub enum SandboxLevel {
    None,     // No sandbox. Tools run with full process privileges.
    Relaxed,  // Workspace-only writes, network allowed.
    Strict,   // Workspace-only writes, no network, isolated namespace.
}

pub struct SandboxConfig {
    pub level: SandboxLevel,
    pub allowed_paths: Vec<PathBuf>,
    pub network_access: bool,
    pub max_cpu_time: Option<Duration>,
    pub max_memory: Option<u64>,
}

This is the policy. The implementation differs per OS; the policy doesn’t.

Linux: bubblewrap

bubblewrap (bwrap) is the gold-standard Linux user-namespace sandbox. It’s what Flatpak uses.

bwrap \
  --ro-bind /usr /usr \
  --ro-bind /lib /lib \
  --ro-bind /lib64 /lib64 \
  --ro-bind /etc /etc \
  --bind ${WORKSPACE} ${WORKSPACE} \
  --unshare-net \
  --unshare-pid \
  --die-with-parent \
  --new-session \
  -- ${TOOL_COMMAND}

bwrap builds a fresh mount namespace, gives the tool a read-only view of the system, a read-write view of the workspace, no network, and a private PID namespace. The tool can’t see or signal anything outside that namespace.

Install: sudo apt install bubblewrap (Debian/Ubuntu) or your distro’s equivalent.

macOS: sandbox-exec

macOS ships an undocumented but stable sandbox interpreter at /usr/bin/sandbox-exec. The policy is a small Scheme-ish language called Seatbelt.

(version 1)
(deny default)

(allow process-exec)
(allow signal (target self))
(allow sysctl-read)
(allow mach-lookup)
(allow file-read* (subpath "/usr"))
(allow file-read* (subpath "/System"))
(allow file-read* (subpath "/Library/Frameworks"))
(allow file-read* file-write* (subpath "{WORKSPACE}"))

(deny network*)

We render the profile from SandboxConfig, write it to a temp file, and invoke sandbox-exec -f profile.sb -- ${TOOL_COMMAND}. No external dependency; macOS ships it.

Windows: Job Objects

Windows has no namespace sandbox in the Unix sense, but it has Job Objects, which let you cap CPU + memory and prevent child processes from escaping the job.

We create the job, set JOBOBJECT_BASIC_LIMIT_INFORMATION:

  • JOB_OBJECT_LIMIT_PROCESS_TIME — CPU time cap.
  • JOB_OBJECT_LIMIT_JOB_MEMORY — memory cap.
  • JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE — kill all processes if parent dies.
  • JOB_OBJECT_LIMIT_BREAKAWAY_OK disabled — child can’t escape.

Filesystem and network restrictions aren’t free here; the Strict policy on Windows is “weaker” than on Linux/macOS, and we document that honestly in docs/SECURITY.md. If your threat model demands the strongest filesystem sandbox, run on Linux.

A single Rust API

let config = SandboxConfig {
    level: SandboxLevel::Strict,
    allowed_paths: vec![workspace.clone()],
    network_access: false,
    max_cpu_time: Some(Duration::from_secs(30)),
    max_memory: Some(512 * 1024 * 1024),
};

let result = sandbox::execute(&config, &tool_command).await?;

sandbox::execute dispatches to the right backend by cfg!(target_os = ...). The policy crosses platforms; the implementation differs.

What the sandbox doesn’t do

Be honest about scope. The sandbox is the last line of defence, not the only one.

  • Input validation comes first. openclaw-core::validation caps message sizes (100 KB), JSON depth (32), attachment sizes (50 MB), attachment counts (10), and rejects null bytes.
  • Tool registration comes second. The agent runtime only invokes tools registered in the ToolRegistry. The LLM can’t conjure a tool that wasn’t there.
  • The sandbox is what runs the registered tool with restricted privileges.

If an attacker can prompt-inject the LLM into calling a tool that does something destructive, but the sandbox stops the tool from touching anything outside the workspace, the attacker accomplished nothing.

Picking your level

LevelBest forWhat it gives up
NoneTrusted internal tools you’ve reviewedProcess isolation entirely
RelaxedTools that genuinely need network accessNetwork-level isolation
StrictUntrusted-looking tools, default for unknown pluginsNetwork access

Set the default to Strict in production. Whitelist looser policies per tool.

Further reading

  • docs/SECURITY.md in the repo for the full threat model.
  • zero-trust-agent-runtime for how the sandbox fits the broader defence-in-depth story.