The default: bubblewrap
Bubblewrap is the same sandboxing primitive Flatpak uses. It's a small setuid-free binary that builds a Linux user-namespace + mount-namespace sandbox using only kernel features. No daemon, no Docker, no root.
Cold start: ~30 ms on a modern x86 box. That's per tool call cheap enough — you can spawn a fresh sandbox for every shell command your agent issues. Each invocation gets a clean tmpfs, a frozen view of the tenant root, and a tight seccomp filter.
bwrap \
--unshare-all \
--share-net=false \
--die-with-parent \
--ro-bind /usr /usr \
--ro-bind /lib /lib \
--ro-bind /bin /bin \
--bind /tenants/acme/sandbox /work \
--chdir /work \
--tmpfs /tmp \
--proc /proc \
--dev /dev \
--seccomp 11 \
-- /bin/bash -c "$AGENT_CMD" The escape hatch: Docker
When the workload is genuinely untrusted — user-submitted scripts in a multi-tenant SaaS, plugins from an unverified ClawHub publisher, code paths your security team flagged — switch to Docker.
Cold start: 200–500 ms (one-time per warm pool) but you get the full
container-isolation suite: seccomp default-deny, cgroups for memory
and CPU caps, dropped capabilities (no CAP_NET_ADMIN,
no CAP_SYS_ADMIN), an unprivileged user inside, and
optional gVisor for kernel-level isolation.
sandbox:
mode: docker # or "bwrap"
image: openclaw/sandbox:base
runtime: runsc # gVisor (optional, requires runsc on host)
memory_limit_mb: 512
cpu_quota: 0.5 # half a vCPU
network: none
read_only: true
drop_caps: [ALL]
add_caps: []
seccomp_profile: /etc/openclaw/seccomp-default.json Configuration: pick per workload
The sandbox mode can be set:
- Globally in the gateway config (default).
- Per tenant in the tenant's config overlay.
- Per agent run by passing
--sandbox dockeron the CLI orsandbox: "docker"in the JSON-RPC params. - Per tool in the tool's metadata.
Most deployments end up using bubblewrap as the default and Docker only for the small set of tools that touch external code.
What the sandbox sees
- Filesystem: read-only host system, writable scratch tmpfs, writable tenant work directory.
- Network: off by default; opt-in per tool via an allow-list of hostnames + ports.
- Process tree: only the sandboxed process; parent gateway PID is hidden.
- Devices:
/dev/null,/dev/urandom; no other devices. - Capabilities: none. Even when Docker is the runtime, the container runs with
--cap-drop=ALL. - Seccomp: default-deny filter; common syscalls allow-listed (read/write/openat/close/exec/fork…).
The web terminal piggy-backs on this
The browser-based web terminal (xterm.js in the UI, node-pty server
side) spawns the shell inside the tenant's sandbox. So a
user logging into https://your-gateway.example.com/terminal
and running rm -rf / deletes nothing outside their
own tenant work directory.