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 (gateway by default, or node when the exec target is node). If sandboxing is turned off, tools.elevated has 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:

SettingKeyValuesDefault
Modeagents.defaults.sandbox.modeoff, non-main, alloff
Scopeagents.defaults.sandbox.scopeagent, session, sharedagent
Backendagents.defaults.sandbox.backenddocker, ssh, openshelldocker

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 always agent:<agentId>:main (or global when session.scope is "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-agent docker/ssh/browser overrides 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.

DockerSSHOpenShell
Where it runsLocal containerAny host reachable via SSHSandbox managed by OpenShell
Setupscripts/sandbox-setup.shSSH key plus target hostOpenShell plugin must be enabled
Workspace modelBind mount or copyRemote canonical (seed once)mirror or remote
Network controldocker.network (default: none)Controlled by the remote hostControlled by OpenShell
Browser sandboxSupportedNot supportedNot yet supported
Bind mountsdocker.bindsN/AN/A
Best forLocal development, full isolationOffloading work to a remote systemManaged 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.json workspace must 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 workspace path. 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 see EACCES when 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_exec and sandbox_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-write shell 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 are bwrap: setting up uid map: Permission denied and bwrap: loopback: Failed RTM_NEWADDR: Operation not permitted. Run openclaw 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=0 is 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 (default true) and autoStartTimeoutMs (default 12s).
  • Sandbox browser containers use a dedicated Docker network (openclaw-sandbox-browser) rather than the global bridge network. Set this with agents.defaults.sandbox.browser.network.
  • agents.defaults.sandbox.browser.cdpSourceRange limits container-edge CDP ingress using a CIDR allowlist (for example 172.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 (default false) 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/knownHostsFile point to existing local files. identityData/certificateData/knownHostsData accept inline strings or SecretRefs, resolved through the normal secrets runtime snapshot, written to temp files with mode 0600, and removed when the SSH session ends. If both a *File and *Data variant are set for the same item, *Data takes 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 recreate removes the per-scope remote root and reseeds from local on the next use. Browser sandboxing is not available on this backend, and sandbox.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:

ValueBehavior
none (default)Tools only see an isolated sandbox workspace located at ~/.openclaw/sandboxes.
roThe agent workspace is mounted as read-only at /agent, which disables write/edit/apply_patch.
rwThe 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 read tool is rooted within the sandbox. With workspaceAccess: "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
  • ro gives the mounted folder read-only access within the sandbox.
  • rw allows 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:

SettingControls
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/:rwOnly 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 (:ro or :rw).
  • By default, OpenClaw blocks dangerous bind sources: system paths (/etc, /proc, /sys, /dev, /root, /boot), Docker socket directories (/run, /var/run, and their docker.sock variants), 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-file still resolves as /var/run/... if run-link points there).
  • Bind targets that shadow the reserved container mount points (/workspace, /agent) are also blocked by default; override with agents.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 :ro unless 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, and scripts/sandbox-browser-setup.sh helper 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 inline docker build commands 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=new applies when browser.headless is active.
  • --no-sandbox --disable-setuid-sandbox applies when browser.noSandbox is active.
  • --disable-3d-apis, --disable-gpu, and --disable-software-rasterizer are set by default; these graphics hardening flags assist containers without GPU access. Set OPENCLAW_BROWSER_DISABLE_GRAPHICS_FLAGS=0 if your workload requires WebGL or other 3D capabilities.
  • --disable-extensions is set by default; use OPENCLAW_BROWSER_DISABLE_EXTENSIONS=0 for workflows that depend on extensions.
  • --renderer-process-limit=2 is the default, managed by OPENCLAW_BROWSER_RENDERER_PROCESS_LIMIT=<N>, where 0 preserves 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.network is "none" (no egress allowed), so package installations will not succeed.
  • docker.network: "container:<id>" is a break-glass option that demands dangerouslyAllowContainerNamespaceJoin: true.
  • readOnlyRoot: true blocks write operations; either set readOnlyRoot: false or build a custom image.
  • To install packages as root, user is required (leave out user or configure user: "0:0").
  • The host process.env is not inherited by sandbox exec. For skill API keys, use agents.defaults.sandbox.docker.env (or a custom image).
  • Entries in agents.defaults.sandbox.docker.env become explicit Docker container environment variables. Anyone with Docker daemon access can view them using metadata commands like docker 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. The workspaceRoot field holds the configured sandbox root; effectiveHostWorkspaceRoot indicates 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",
      },
    },
  },
}