Sandboxing in OpenClaw: Modes, Scopes, and Workspace Access
Learn how OpenClaw sandboxing limits tool execution to restrict blast radius. This page covers sandbox modes, scopes, workspace access, and images for Gateway administrators.
OpenClaw can execute tools inside a sandbox backend to limit the blast radius of an incident. This feature is disabled by default and is governed by agents.defaults.sandbox (applied globally) or agents.entries.*.sandbox (set per agent). The Gateway process always remains on the host machine; only tool execution is moved into the sandbox environment when the feature is active.
Note
This does not provide a perfect security barrier, but it does significantly restrict filesystem and process access if the model behaves incorrectly.
What gets sandboxed
- Tool execution:
exec,read,write,edit,apply_patch,process, and others. - The optional sandboxed browser (
agents.defaults.sandbox.browser).
Items that are not sandboxed:
- The Gateway process itself.
- Any tool that has been explicitly permitted to run outside the sandbox using
tools.elevated. Elevated exec bypasses sandboxing and runs on the configured escape path (gatewayby default, ornodewhen the exec target isnode). If sandboxing is turned off,tools.elevatedhas no effect because exec already runs on the host. For more details, see Elevated Mode.
Modes, scope, and backend
Three separate configuration options determine sandbox behavior:
| Setting | Key | Values | Default |
|---|---|---|---|
| Mode | agents.defaults.sandbox.mode | off, non-main, all | off |
| Scope | agents.defaults.sandbox.scope | agent, session, shared | agent |
| Backend | agents.defaults.sandbox.backend | docker, ssh, openshell | docker |
Mode defines when sandboxing is applied:
off: sandboxing is completely off.non-main: every session is sandboxed except for the agent's main session. The main session key is alwaysagent:<agentId>:main(orglobalwhensession.scopeis"global"); this key cannot be changed. Group and channel sessions use their own keys, so they are always treated as non main sessions and are sandboxed.all: all sessions run inside a sandbox.
Scope determines how many containers or environments are created:
agent: one container is created per agent.session: one container is created per session.shared: a single container is shared by all sandboxed sessions. Per-agentdocker/ssh/browseroverrides are ignored when this scope is active.
Backend selects the runtime that executes sandboxed tools. SSH specific settings are located under agents.defaults.sandbox.ssh; OpenShell specific settings are under plugins.entries.openshell.config.
| Docker | SSH | OpenShell | |
|---|---|---|---|
| Where it runs | Local container | Any host reachable via SSH | Sandbox managed by OpenShell |
| Setup | scripts/sandbox-setup.sh | SSH key plus target host | OpenShell plugin must be enabled |
| Workspace model | Bind mount or copy | Remote canonical (seed once) | mirror or remote |
| Network control | docker.network (default: none) | Controlled by the remote host | Controlled by OpenShell |
| Browser sandbox | Supported | Not supported | Not yet supported |
| Bind mounts | docker.binds | N/A | N/A |
| Best for | Local development, full isolation | Offloading work to a remote system | Managed remote sandboxes with optional bidirectional sync |
Docker backend
Once sandboxing is activated, Docker is the default backend. It runs tools and sandbox browsers locally using the Docker daemon socket (/var/run/docker.sock); isolation is provided by Docker namespaces.
Defaults: network: "none" (no outbound network access), readOnlyRoot: true, capDrop: ["ALL"], image openclaw-sandbox:bookworm-slim.
To make host GPUs available, configure agents.defaults.sandbox.docker.gpus (or the per-agent override) with a value such as "all" or "device=GPU-uuid". This setting maps to Docker's --gpus flag and needs a compatible host runtime, for example the NVIDIA Container Toolkit.
Warning
Limitations with Docker-out-of-Docker (DooD)
When the OpenClaw Gateway itself runs as a Docker container, it manages sibling sandbox containers through the host's Docker socket (DooD). This creates a path mapping limitation:
- Host paths required in config: The values for
openclaw.jsonworkspacemust use the host's absolute path (like/home/user/.openclaw/workspaces), not the path inside the Gateway container. The Docker daemon resolves paths against the host OS namespace, not the Gateway's own namespace.- Volume map must match: The Gateway process also writes heartbeat and bridge files to that same
workspacepath. Provide the Gateway container with an identical volume mapping (-v /home/user/.openclaw:/home/user/.openclaw) so the host path resolves correctly from within the Gateway container as well. If mappings are mismatched, you will seeEACCESwhen the Gateway attempts to write its heartbeat.- Codex code mode: While an OpenClaw sandbox is active, OpenClaw disables Codex app-server native Code Mode, user MCP servers, and app-backed plugin execution for that turn (these run from the Gateway-host app-server process, not the OpenClaw sandbox backend). The exception is when the sandbox tool policy exposes the required tools and you opt into the experimental sandbox exec-server path. Shell access then goes through OpenClaw sandbox-backed tools like
sandbox_execandsandbox_process. Do not mount the host Docker socket into agent sandbox containers or custom Codex sandboxes. See Codex Harness for complete details.On Ubuntu or AppArmor hosts with Docker sandbox mode enabled, Codex app-server
workspace-writeshell execution requires unprivileged user namespaces inside the sandbox container. This can fail before shell startup if the service user cannot create them. An unprivileged network namespace is also needed when Docker sandbox egress is disabled (network: "none", the default). Common indicators arebwrap: setting up uid map: Permission deniedandbwrap: loopback: Failed RTM_NEWADDR: Operation not permitted. Runopenclaw doctor; if it shows a Codex bwrap namespace probe failure, use an AppArmor profile that grants the necessary namespaces to the OpenClaw service process.kernel.apparmor_restrict_unprivileged_userns=0is a host-wide fallback with security implications; only use it when that host posture is acceptable.
Sandboxed browser
- The sandbox browser starts automatically (ensuring CDP is reachable) when a browser tool requires it. Configure this with
agents.defaults.sandbox.browser.autoStart(defaulttrue) andautoStartTimeoutMs(default 12s). - Sandbox browser containers use a dedicated Docker network (
openclaw-sandbox-browser) rather than the globalbridgenetwork. Set this withagents.defaults.sandbox.browser.network. agents.defaults.sandbox.browser.cdpSourceRangelimits container-edge CDP ingress using a CIDR allowlist (for example172.21.0.1/32).- noVNC observer access is password-protected by default. OpenClaw generates a short-lived token URL that serves a local bootstrap page and opens noVNC with the password in the URL fragment (not in query strings or header logs).
agents.defaults.sandbox.browser.allowHostControl(defaultfalse) allows sandboxed sessions to explicitly target the host browser.- Optional allowlists control
target: "custom":allowedControlUrls,allowedControlHosts,allowedControlPorts.
SSH backend
Use backend: "ssh" to sandbox exec, file tools, and media reads on any machine accessible via SSH.
{
agents: {
defaults: {
sandbox: {
mode: "all",
backend: "ssh",
scope: "session",
workspaceAccess: "rw",
ssh: {
target: "user@gateway-host:22",
workspaceRoot: "/tmp/openclaw-sandboxes",
strictHostKeyChecking: true,
updateHostKeys: true,
identityFile: "~/.ssh/id_ed25519",
certificateFile: "~/.ssh/id_ed25519-cert.pub",
knownHostsFile: "~/.ssh/known_hosts",
// Or use SecretRefs / inline contents instead of local files:
// identityData: { source: "env", provider: "default", id: "SSH_IDENTITY" },
// certificateData: { source: "env", provider: "default", id: "SSH_CERTIFICATE" },
// knownHostsData: { source: "env", provider: "default", id: "SSH_KNOWN_HOSTS" },
},
},
},
},
}
Defaults: command: "ssh", workspaceRoot: "/tmp/openclaw-sandboxes", strictHostKeyChecking: true, updateHostKeys: true.
- Lifecycle: OpenClaw creates a per-scope remote root under
sandbox.ssh.workspaceRoot. On first use after a create or recreate operation, it seeds that remote workspace from the local workspace once. After that,exec,read,write,edit,apply_patch, prompt media reads, and inbound media staging operate directly on the remote workspace over SSH. OpenClaw does not automatically sync remote changes back to the local workspace. - Authentication material:
identityFile/certificateFile/knownHostsFilepoint to existing local files.identityData/certificateData/knownHostsDataaccept inline strings or SecretRefs, resolved through the normal secrets runtime snapshot, written to temp files with mode0600, and removed when the SSH session ends. If both a*Fileand*Datavariant are set for the same item,*Datatakes precedence for that session. - Remote-canonical consequences: After the initial seed, the remote SSH workspace becomes the actual sandbox state. Host-local edits made outside OpenClaw after seeding are not visible remotely until you recreate the sandbox.
openclaw sandbox recreateremoves the per-scope remote root and reseeds from local on the next use. Browser sandboxing is not available on this backend, andsandbox.docker.*settings do not apply to it.
OpenShell backend
When working with tools in an OpenShell-managed remote environment, backend: "openshell" provides sandboxing capabilities. The same SSH transport and remote filesystem bridge used by the generic SSH backend are reused by OpenShell, which also introduces OpenShell lifecycle management (sandbox create/get/delete/ssh-config) along with an optional mirror workspace sync mode.
{
agents: {
defaults: {
sandbox: {
mode: "all",
backend: "openshell",
scope: "session",
workspaceAccess: "rw",
},
},
},
plugins: {
entries: {
openshell: {
enabled: true,
config: {
from: "openclaw",
mode: "remote", // mirror | remote
},
},
},
},
}
By default, mode: "mirror" treats the local workspace as the authoritative source: before exec, OpenClaw syncs local content into the sandbox, and after execution it syncs back. With mode: "remote", the remote workspace receives a one-time seed from local, after which exec/read/write/edit/apply_patch operate directly on the remote workspace without any return sync; any local modifications made after the seed remain hidden until you run openclaw sandbox recreate. When using scope: "agent" or scope: "shared", that remote workspace is shared at the same scope. Two current limitations exist: the sandbox browser is not yet supported, and sandbox.docker.binds does not function with this backend.
For openclaw sandbox list/recreate/prune operations, OpenShell runtimes are handled identically to Docker runtimes, with the prune logic being backend-aware.
See OpenShell for complete prerequisites, configuration reference, workspace-mode comparison, and lifecycle details.
Workspace access
The sandbox's visibility is governed by agents.defaults.sandbox.workspaceAccess:
| Value | Behavior |
|---|---|
none (default) | Tools only see an isolated sandbox workspace located at ~/.openclaw/sandboxes. |
ro | The agent workspace is mounted as read-only at /agent, which disables write/edit/apply_patch. |
rw | The agent workspace is mounted with read/write access at /workspace. |
When using the OpenShell backend, mirror mode continues to treat the local workspace as canonical between execution turns, remote mode makes the remote OpenShell workspace canonical after the initial seed, and workspaceAccess: "ro"/"none" still apply the same write behavior restrictions.
Inbound media gets copied into the active sandbox workspace (media/inbound/*).
Note
Skills: the
readtool is rooted within the sandbox. WithworkspaceAccess: "none", OpenClaw copies eligible skills into the sandbox workspace (.../skills) for reading. Under"rw", workspace skills can be accessed from/workspace/skills, while eligible managed, bundled, or plugin skills are materialized into the generated read-only path/workspace/.openclaw/sandbox-skills/skills.
Multiple folders for one agent
Docker bind mounts are useful when a sandboxed agent requires more than just its primary workspace. Each entry maps a host folder to a container path with an explicit access mode:
host-directory:container-directory:ro
host-directory:container-directory:rw
rogives the mounted folder read-only access within the sandbox.rwallows sandboxed tools and processes to modify the host folder.- The container path is what the agent uses. Host paths are not automatically exposed.
This configuration provides the research agent with a writable primary workspace, read-only reference material at /reference, and a separate writable output folder at /drafts:
{
agents: {
defaults: {
sandbox: {
mode: "all",
scope: "agent",
},
},
list: [
{
id: "research",
workspace: "/srv/openclaw/research-workspace",
sandbox: {
workspaceAccess: "rw",
docker: {
binds: ["/srv/shared/reference:/reference:ro", "/srv/shared/drafts:/drafts:rw"],
// Required because these sources are outside the agent workspace.
dangerouslyAllowExternalBindSources: true,
},
},
},
],
},
}
workspaceAccess and bind modes operate independently:
| Setting | Controls |
|---|---|
workspaceAccess: "none" | Creates an isolated sandbox workspace without exposing the agent workspace. |
workspaceAccess: "ro" | Mounts the agent workspace as read-only at /agent. |
workspaceAccess: "rw" | Mounts the agent workspace with read/write access at /workspace. |
docker.binds entry :ro/:rw | Only controls that additional host folder at its configured container path. |
Modifying workspaceAccess has no effect on an extra bind from ro to rw, or the reverse. Global and per-agent docker.binds are combined. Use scope: "agent" or "session" for per-agent binds; scope: "shared" disregards all per-agent Docker overrides and applies only global binds.
Bind mounts serve as the supported multi-folder boundary because Docker uses mount isolation to build the container's filesystem view, and the ro/rw mode applies to every process within the sandbox. This boundary covers exec, filesystem utilities, child processes, and libraries without needing duplicate path-authorization checks across every OpenClaw code path. A host-side path allowlist cannot deliver the same complete boundary when an allowed shell or dependency can access files directly.
The opt-in dangerouslyAllowExternalBindSources only permits sources outside the workspace roots. It does not disable OpenClaw's checks for blocked system paths, credentials, Docker sockets, symlink parents, or reserved targets. Prefer the smallest folder, use ro unless writes are required, and recreate the sandbox after changing mounts:
openclaw sandbox recreate --agent research
Other bind behavior
agents.defaults.sandbox.docker.binds configures global mounts. The format uses the same host:container:mode structure (for example, "/home/user/source:/source:rw").
agents.defaults.sandbox.browser.binds mounts extra host directories into the sandbox browser container only. When set (including []), it replaces docker.binds for the browser container; when omitted, the browser container falls back to docker.binds.
{
agents: {
defaults: {
sandbox: {
docker: {
binds: ["/home/user/source:/source:ro", "/var/data/myapp:/data:ro"],
},
},
},
list: [
{
id: "build",
sandbox: {
docker: {
binds: ["/mnt/cache:/cache:rw"],
},
},
},
],
},
}
Warning
Bind security
- Binds bypass the sandbox filesystem: they expose host paths with whatever mode you set (
:roor:rw).- By default, OpenClaw blocks dangerous bind sources: system paths (
/etc,/proc,/sys,/dev,/root,/boot), Docker socket directories (/run,/var/run, and theirdocker.sockvariants), and common home-directory credential roots (~/.aws,~/.cargo,~/.config,~/.docker,~/.gnupg,~/.netrc,~/.npm,~/.ssh).- Validation normalizes the source path, then resolves it again through the deepest existing ancestor before re-checking blocked paths and allowed roots, so symlink-parent escapes fail closed even when the final leaf does not yet exist (for example,
/workspace/run-link/new-filestill resolves as/var/run/...ifrun-linkpoints there).- Bind targets that shadow the reserved container mount points (
/workspace,/agent) are also blocked by default; override withagents.defaults.sandbox.docker.dangerouslyAllowReservedContainerTargets: true.- Bind sources outside the workspace/agent-workspace allowlisted roots are blocked by default; override with
agents.defaults.sandbox.docker.dangerouslyAllowExternalBindSources: true. Allowed roots are canonicalized the same way, so a path that only appears inside the allowlist before symlink resolution is still rejected as outside allowed roots.- Sensitive mounts (secrets, SSH keys, service credentials) should be
:rounless absolutely required.- Combine with
workspaceAccess: "ro"if you only need read access to the workspace; bind modes stay independent.- See Sandbox vs Tool Policy vs Elevated for how binds interact with tool policy and elevated exec.
Images and setup
Default Docker image: openclaw-sandbox:bookworm-slim
Note
Source checkout vs npm install
The
scripts/sandbox-setup.sh,scripts/sandbox-common-setup.sh, andscripts/sandbox-browser-setup.shhelper scripts are only available when running from a source checkout. They are not included in the npm package.If you installed OpenClaw via
npm install -g openclaw, use the inlinedocker buildcommands shown below instead.
Build the default image
When working from a source checkout:
scripts/sandbox-setup.sh
If installing via npm (no source checkout required):
docker build -t openclaw-sandbox:bookworm-slim - <<'DOCKERFILE'
FROM debian:bookworm-slim
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y --no-install-recommends \
bash ca-certificates curl git jq python3 ripgrep \
&& rm -rf /var/lib/apt/lists/*
RUN useradd --create-home --shell /bin/bash sandbox
USER sandbox
WORKDIR /home/sandbox
CMD ["sleep", "infinity"]
DOCKERFILE
The default image lacks Node. When a skill requires Node or another runtime, either create a custom image or install it with sandbox.docker.setupCommand, which demands network egress, a writable root filesystem, and the root user.
OpenClaw will not silently fall back to plain debian:bookworm-slim when openclaw-sandbox:bookworm-slim is absent. Sandbox runs aimed at the default image fail immediately with a build instruction until you create it, because the bundled image contains python3 for the sandbox's write and edit helpers.
Optional: build the common image
For a sandbox image with more functionality and common tools, such as curl, jq, Node 24, pnpm, python3, and git:
From a source checkout:
scripts/sandbox-common-setup.sh
After an npm install, first build the default image as described above, then layer the common image on top using scripts/docker/sandbox/Dockerfile.common from the repository.
After that, assign agents.defaults.sandbox.docker.image the value openclaw-sandbox-common:bookworm-slim.
Optional: build the sandbox browser image
From a source checkout:
scripts/sandbox-browser-setup.sh
When using npm install, build with scripts/docker/sandbox/Dockerfile.browser from the repository.
By default, Docker sandbox containers operate with no network access. Use agents.defaults.sandbox.docker.network to override this.
Sandbox browser Chromium defaults
The included sandbox browser image applies conservative Chromium startup flags suitable for containerized environments:
--remote-debugging-address=127.0.0.1--remote-debugging-port=<derived from OPENCLAW_BROWSER_CDP_PORT>--user-data-dir=${HOME}/.chrome--no-first-run--no-default-browser-check--disable-dev-shm-usage--disable-background-networking--disable-breakpad--disable-crash-reporter--no-zygote--metrics-recording-only--password-store=basic--use-mock-keychain--headless=newapplies whenbrowser.headlessis active.--no-sandbox --disable-setuid-sandboxapplies whenbrowser.noSandboxis active.--disable-3d-apis,--disable-gpu, and--disable-software-rasterizerare set by default; these graphics hardening flags assist containers without GPU access. SetOPENCLAW_BROWSER_DISABLE_GRAPHICS_FLAGS=0if your workload requires WebGL or other 3D capabilities.--disable-extensionsis set by default; useOPENCLAW_BROWSER_DISABLE_EXTENSIONS=0for workflows that depend on extensions.--renderer-process-limit=2is the default, managed byOPENCLAW_BROWSER_RENDERER_PROCESS_LIMIT=<N>, where0preserves Chromium's original setting.
For a different runtime configuration, use a custom browser image and supply your own entrypoint. For local (non-container) Chromium setups, apply additional startup flags with browser.extraArgs.
Network security defaults
network: "host"is disallowed.network: "container:<id>"is disallowed by default (risk of namespace join bypass).- Emergency override:
agents.defaults.sandbox.docker.dangerouslyAllowContainerNamespaceJoin: true.
Docker installations and the containerized gateway are located here: Docker
For Docker-based gateway deployments, sandbox configuration can be initialized through scripts/docker/setup.sh. To activate that approach, set OPENCLAW_SANDBOX=1 (alternatively true/yes/on). Use OPENCLAW_DOCKER_SOCKET to change the socket location. Complete setup instructions and environment variable reference: Docker.
setupCommand (one-time container setup)
After the sandbox container is created (not on each execution), setupCommand runs once. Inside the container, it executes via sh -lc.
Paths:
- Global:
agents.defaults.sandbox.docker.setupCommand - Per-agent:
agents.entries.*.sandbox.docker.setupCommand
Common pitfalls
- The default
docker.networkis"none"(no egress allowed), so package installations will not succeed. docker.network: "container:<id>"is a break-glass option that demandsdangerouslyAllowContainerNamespaceJoin: true.readOnlyRoot: trueblocks write operations; either setreadOnlyRoot: falseor build a custom image.- To install packages as root,
useris required (leave outuseror configureuser: "0:0"). - The host
process.envis not inherited by sandbox exec. For skill API keys, useagents.defaults.sandbox.docker.env(or a custom image). - Entries in
agents.defaults.sandbox.docker.envbecome explicit Docker container environment variables. Anyone with Docker daemon access can view them using metadata commands likedocker inspect. If that metadata visibility is unacceptable, use a custom image, a mounted secret file, or another secret delivery mechanism.
Tool policy and escape hatches
Tool allow/deny policies are evaluated before sandbox rules. A tool denied globally or per-agent cannot be re-enabled by sandboxing.
tools.elevated provides an explicit escape mechanism, executing exec outside the sandbox environment (gateway by default, or node when the exec target is node). /exec directives apply only to authorized senders and remain active for the duration of a session; to permanently disable exec, use a tool policy deny (refer to Sandbox vs Tool Policy vs Elevated).
Debugging:
- Sandbox containers, their status, image match, age, idle time, and associated session or agent are shown by
openclaw sandbox list. openclaw sandbox explain [--session <key>] [--agent <id>]reveals the effective sandbox mode, host workspace, runtime workdir, Docker mounts, tool policy, and fix-it configuration keys. TheworkspaceRootfield holds the configured sandbox root;effectiveHostWorkspaceRootindicates the actual active workspace location.- Containers and environments are removed by
openclaw sandbox recreate [--all | --session <key> | --agent <id>] [--browser] [--force], forcing recreation with the current configuration on the next use. - For the "why is this blocked?" mental model, see Sandbox vs Tool Policy vs Elevated.
Multi-agent overrides
Sandbox and tool settings can be overridden per agent: agents.entries.*.sandbox and agents.entries.*.tools (along with agents.entries.*.tools.sandbox.tools for sandbox tool policy). Precedence details are in Multi-Agent Sandbox & Tools.
Minimal enable example
{
agents: {
defaults: {
sandbox: {
mode: "non-main",
scope: "session",
workspaceAccess: "none",
},
},
},
}
Related
- Multi-Agent Sandbox & Tools -- per-agent overrides and their precedence order
- OpenShell -- managed sandbox backend configuration, workspace modes, and reference documentation
- Sandbox configuration
- Sandbox vs Tool Policy vs Elevated -- troubleshooting "why is this blocked?"
- Security