> ## Documentation Index
> Fetch the complete documentation index at: https://docs.langbot.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Sandbox Configuration

> Configure LangBot Box Runtime with Docker, nsjail, E2B, or the explicitly enabled non-isolated Host backend.

Box settings are owned by the LangBot main process; Box Runtime performs the actual execution. For most deployments, you only need to choose a backend, set the workspace directory, and pick a security profile. Docker, nsjail, and E2B provide sandbox environments; the Host backend runs commands directly on the Box Runtime host.

<Note>
  All `box.*` settings on this page live in `data/config.yaml`. See [System Environment Settings](../../deploy/settings) for the file's location and how it's loaded.
</Note>

## Recommended Configuration

```yaml theme={null}
box:
  enabled: true
  backend: 'local'        # auto-pick from Docker / Nsjail
  local:
    profile: 'default'
    host_root: './data/box'
    skills_root: 'skills'
  docker:
    cpu_limit_enabled: true
```

* `enabled`: master switch. When `false`, all Box-dependent features are disabled (sandbox tools, skill add/edit, stdio MCP hosting).
* `backend`: see "Backend Selection" below.
* `local.profile`: security profile — controls network, mounts, and resource limits.
* `local.host_root`: host workspace directory, mapped to `/workspace` inside the sandbox.
* `local.skills_root`: skill package directory; relative paths resolve under `host_root` (defaults to `host_root/skills`).
* `docker.cpu_limit_enabled`: whether Docker sandbox containers get a CPU limit; set to `false` to omit `docker run --cpus` while keeping memory and PID limits.

<Note>
  Skills are loaded only from the Box-managed skill store. When Box Runtime or the backend is unavailable, the skill list is empty and create/edit/`register_skill` are disabled — there's no fallback to `data/skills/`.
</Note>

## Backend Selection

Box can run in **local containers**, in a **cloud sandbox**, or as a **process on the Box Runtime host**. `box.backend` picks which:

| `backend` | Where it runs | Behavior |
| - | - | - |
| `local` (default) | Local containers | Auto-pick from Docker / Nsjail (Docker preferred) |
| `docker` | Local containers | Force Docker; requires the Docker daemon |
| `nsjail` | Local containers | Force Nsjail (Linux only); no custom image support |
| `e2b` | Cloud | Use [E2B](https://e2b.dev) cloud sandbox; requires API key |
| `host` | Box Runtime host | Start local processes with the Box Runtime user's permissions; requires POSIX and `sh` |

`local` is shorthand for "auto-pick", not a fourth backend sitting next to `docker`/`nsjail`. It only tries Docker and Nsjail and **never auto-selects `host`**. Local backends use workspace settings from `box.local.*`; the cloud backend uses `box.e2b.*`.

<Warning>
  `host` is **not a sandbox**. Commands have the Box Runtime user's host permissions, with no filesystem, process, network, namespace, cgroup, rootfs, or image isolation. Read-only mounts and hard disk quotas are not guaranteed. Use it only for single-user local development with trusted input; never use it for public services, shared hosts, or untrusted code.
</Warning>

<Note>
  `backend` is a hard selection. Setting `docker` and finding Docker unavailable does **not** fall back to Nsjail or E2B — only `local` auto-fans-out.

  `BOX__BACKEND` environment variable overrides `box.backend` (highest priority).
</Note>

## Security Profiles

`box.local.profile` controls network, mounts, and resource limits for the Docker / Nsjail local sandbox backends:

| Profile | Network | Mounts | Resources | When to use |
| - | - | - | - | - |
| `default` | Off | Read-write | Default limits | Default choice |
| `offline_readonly` | Off | Read-only | Stricter | Reading untrusted files |
| `network_basic` | Basic network | Read-write | Default limits | API access or dependency install |
| `network_extended` | Full network | Read-write | Relaxed | Development, debugging, complex tasks |

Prefer least privilege: skip the network unless you need it (`default` / `offline_readonly`); only add necessary directories to `allowed_mount_roots`.

<Note>
  The Host backend does not enforce these isolation policies. A configured profile, CPU / memory / PID limit, read-only rootfs, or disabled network does not create a sandbox boundary around host processes.
</Note>

## Local Workspace Configuration (box.local.\*)

Docker, Nsjail, and Host share workspace path settings. Isolation and resource settings only take effect when the selected sandbox backend can enforce them:

| Setting | Default | Notes |
| - | - | - |
| `local.profile` | `default` | Docker / Nsjail security profile; Host provides no matching isolation |
| `local.image` | empty | Docker-only custom image; empty = use profile default |
| `local.host_root` | `./data/box` | Host workspace base, mapped to `/workspace` inside the sandbox |
| `local.default_workspace` | empty | Default workspace name; empty = `<host_root>/default` |
| `local.skills_root` | `skills` | Skill package directory; relative paths resolve under `host_root` |
| `local.allowed_mount_roots` | `[host_root]` | Allowlist of host directories the Agent can mount |
| `local.workspace_quota_mb` | `null` | Workspace disk quota (MB); `null` = use profile default; Host provides no hard quota guarantee |
| `default_memory_mb` | `1536` | Memory limit for each MCP stdio process (MB); Host cannot enforce it through cgroups |

## Host Backend (Trusted Local Development)

Host is the lowest-dependency local option and does not require Docker or Nsjail. It must be selected explicitly:

```yaml theme={null}
box:
  enabled: true
  backend: 'host'
  local:
    host_root: './data/box'
    skills_root: 'skills'
```

The WebUI, Agent tools, and calling conventions stay the same: session scopes, `exec/read/write/edit/glob/grep`, Skills, and stdio MCP all use the existing Box APIs. Only the execution layer changes:

* `/workspace`, `workdir`, and Skill mount paths are translated to real paths on the Box Runtime host.
* Child processes inherit only a minimal path, locale, and terminal environment plus variables explicitly supplied by the request. LangBot / Box control secrets are not inherited automatically. This reduces accidental leakage but does not provide filesystem isolation.
* `exec` and managed processes start in separate process groups. Timeouts, cancellation, process stop, session deletion, and Runtime shutdown terminate the corresponding process trees.
* Idle cleanup, managed-process keepalive, and `persistent` rules match the other backends.
* Status responses include `unsafe_direct_execution: true` so operators can detect the lack of sandbox isolation.

<Note>
  Host means the environment **where Box Runtime runs**. With LangBot's local stdio-managed Box Runtime, that is the current machine. If Box Runtime itself runs in a container, commands run directly in that container; Host does not automatically break through to the physical host.
</Note>

The Host MVP supports POSIX systems with `sh` (Linux / macOS). Use WSL on Windows; a native Windows process backend is not yet supported.

When Host is used through an external WebSocket Box Runtime, set the same strong random `LANGBOT_BOX_CONTROL_TOKEN` on both LangBot and Box Runtime. The locally managed stdio Runtime needs no extra token configuration. Cloud environments with managed-sandbox admission still validate isolation capabilities and the required backend; Host does not bypass those checks.

## Box Sandbox Memory Configuration

`box.default_memory_mb` sets the nsjail cgroup memory cap for every stdio-mode MCP server process.

The Host backend has no cgroup memory isolation, so this value cannot limit MCP processes running on Host.

| Setting | Notes | Default |
| - | - | - |
| `box.default_memory_mb` | MCP process memory cap (MB) | 1536 |

Can be set in config.yaml or via the `BOX__DEFAULT_MEMORY_MB` environment variable.

**When to adjust:**

* Node.js MCP servers (launched via npx/bunx): V8 + WebAssembly module initialisation needs significant memory; keep at ≥ 1536 MB
* Python MCP servers (launched via uvx): 512 MB is usually enough, but the default is fine too
* When memory is exhausted, the process is forcibly killed (return\_code=137), logged as "Box managed process exited unexpectedly"

**Per-server override:** Set `box.memory_mb` in the individual MCP server config to override the global default.

## Docker Backend Configuration (box.docker.\*)

| Setting | Default | Notes |
| - | - | - |
| `docker.cpu_limit_enabled` | `true` | Docker backend only. When `false`, sandbox containers start without `--cpus`; `--memory` and `--pids-limit` still apply. |

## Cloud Backend Configuration (box.e2b.\*)

Configure after setting `backend: 'e2b'`:

| Setting | Default | Notes |
| - | - | - |
| `e2b.api_key` | empty | E2B API key; can also be set via `E2B_API_KEY` |
| `e2b.api_url` | empty | Self-hosted E2B endpoint; can also be set via `E2B_API_URL` |
| `e2b.template` | empty | Default E2B template ID |

E2B doesn't need Docker or Nsjail on the host — every execution goes through the remote sandbox.

## Docker Compose Deployment

In Docker Compose, sandbox settings live on the `langbot` service. LangBot forwards them to `langbot_box` via INIT RPC at startup.

```yaml theme={null}
services:
  langbot_box:
    image: rockchin/langbot:latest
    container_name: langbot_box
    profiles: ["box", "all"]
    volumes:
      - ${LANGBOT_BOX_ROOT:-${PWD}/data/box}:${LANGBOT_BOX_ROOT:-${PWD}/data/box}
      - /var/run/docker.sock:/var/run/docker.sock
    command: ["uv", "run", "--no-sync", "-m", "langbot_plugin.cli.__init__", "box"]

  langbot:
    image: rockchin/langbot:latest
    volumes:
      - ./data:/app/data
    environment:
      - BOX__LOCAL__HOST_ROOT=${LANGBOT_BOX_ROOT:-${PWD}/data/box}
      - BOX__LOCAL__SKILLS_ROOT=skills
      - BOX__LOCAL__ALLOWED_MOUNT_ROOTS=${LANGBOT_BOX_ROOT:-${PWD}/data/box}
      - BOX__DOCKER__CPU_LIMIT_ENABLED=${LANGBOT_BOX_DOCKER_CPU_LIMIT_ENABLED:-true}
```

<Warning>
  `langbot_box` needs the Docker daemon. Only mount `docker.sock` in trusted environments, and keep the Box root path identical on the host and inside the `langbot_box` container.
</Warning>

To point LangBot at an externally-managed Box Runtime (e.g. a remote host), use `box.runtime.endpoint`:

```yaml theme={null}
box:
  runtime:
    endpoint: 'ws://192.168.1.10:5410'
```

## Environment Variables

| Environment variable | Maps to |
| - | - |
| `BOX__ENABLED` | `box.enabled` |
| `BOX__BACKEND` | `box.backend` |
| `BOX__LOCAL__PROFILE` | `box.local.profile` |
| `BOX__LOCAL__IMAGE` | `box.local.image` |
| `BOX__LOCAL__HOST_ROOT` | `box.local.host_root` |
| `BOX__LOCAL__DEFAULT_WORKSPACE` | `box.local.default_workspace` |
| `BOX__LOCAL__SKILLS_ROOT` | `box.local.skills_root` |
| `BOX__LOCAL__ALLOWED_MOUNT_ROOTS` | `box.local.allowed_mount_roots`, comma-separated |
| `BOX__LOCAL__WORKSPACE_QUOTA_MB` | `box.local.workspace_quota_mb` |
| `BOX__DEFAULT_MEMORY_MB` | `box.default_memory_mb` |
| `BOX__DOCKER__CPU_LIMIT_ENABLED` | `box.docker.cpu_limit_enabled` |
| `BOX__E2B__API_KEY` | `box.e2b.api_key` |
| `BOX__E2B__API_URL` | `box.e2b.api_url` |
| `BOX__E2B__TEMPLATE` | `box.e2b.template` |

<Note>
  Don't set `BOX__*` or `LANGBOT_BOX_*` on the `langbot_box` service — Box Runtime doesn't read them directly. Its configuration arrives over INIT RPC from LangBot.
</Note>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.