Skip to content

Code Interpreter with OpenSandbox

lqdflying edited this page Oct 16, 2026 · 5 revisions

Sandbox (OpenSandbox)

The Sandbox builtin (identifier lobe-sandbox, formerly Code Interpreter) gives the model a Linux container for the conversation. OpenSandbox is the only backend. The DifySandbox backend, SANDBOX_PROVIDER, and CODE_INTERPRETER_SANDBOX_URL / _API_KEY are gone.

The model can run shell commands (including background servers), git, Node.js, and Python, and it can read, write, edit, list, and export files. The working directory is /tmp/workspace. Files, installs, and background processes carry over between calls in that conversation. Python variables do not. Graphile runs these calls after you close the tab. Enabling Sandbox does not force the turn onto the browser.

Leave the server unset if you do not need it. ChatHub still boots. A Sandbox call then returns a configuration error and the model continues.

Setup below is a single-host Docker deployment with runc. Kata is optional and untested. Full procedure for the image, the server, and ChatHub's env follows. Maintainer notes live in the repository under openwiki/architecture/sandbox.md.

What the model can call

API Use
runCommand A bash command. background: true starts a server or watcher and returns a command id.
getCommandOutput / stopCommand Read or stop a background command.
runPython Python in a new process. The old API name is python.
readFile / writeFile / editFile / listFiles Files in the sandbox. Relative paths start at /tmp/workspace. An absolute path is inside the container, not on the ChatHub host.
exportFile Copy sandbox files into the chat so the user can open them.

packages on runPython does not install anything. Use runCommand with pip or npm, or add the package to the image. An install lasts only while that sandbox lives.

Saving a file on iPhone or iPad

In the installed app, tap the file link in the reply, or Download on the file card. A reply link that points at public storage uses the same save. iOS opens the share sheet. Choose Save to Files. The chat stays open.

If the file takes a moment to prepare, the app asks you to confirm, then opens the share sheet. Desktop browsers and desktop installed apps download the file directly.

Before you start

  • A ChatHub image that includes this Sandbox tool.
  • Root on the Docker host.
  • A free subnet for sandboxes. This guide uses 172.30.0.0/24.
  • A free host port range. This guide uses 40000-41000.
  • The Docker network ChatHub already uses. This guide uses 1panel-network as the example. The OpenSandbox server must join that network. Do not publish the server port.

1. Sandbox network and firewall

Sandboxes use their own bridge, with inter-container traffic off.

docker network inspect $(docker network ls -q) \
  --format '{{.Name}} {{range .IPAM.Config}}{{.Subnet}}{{end}}' | grep '172\.30\.' || true

docker network create --driver bridge --subnet 172.30.0.0/24 \
  --opt com.docker.network.bridge.enable_icc=false chathub-sbx

If the first command prints a line, pick another subnet and use it everywhere this guide says 172.30.0.0/24 and 172.30.0.1.

These iptables rules keep sandbox code off the host, private ranges, cloud metadata (169.254.0.0/16), and other sandboxes. They are lost on reboot, so install them as a service. Docker's DOCKER-USER chain must exist (iptables -S DOCKER-USER prints -N DOCKER-USER).

cat > /usr/local/sbin/chathub-sbx-firewall.sh <<'EOF'
#!/bin/sh
set -eu
SBX=172.30.0.0/24
CHAIN=CHATHUB-SBX

iptables -N "$CHAIN" 2>/dev/null || iptables -F "$CHAIN"
iptables -A "$CHAIN" -m conntrack --ctstate ESTABLISHED,RELATED -j RETURN
iptables -A "$CHAIN" -d 10.0.0.0/8 -j DROP
iptables -A "$CHAIN" -d 172.16.0.0/12 -j DROP
iptables -A "$CHAIN" -d 192.168.0.0/16 -j DROP
iptables -A "$CHAIN" -d 169.254.0.0/16 -j DROP

iptables -C DOCKER-USER -s "$SBX" -j "$CHAIN" 2>/dev/null \
  || iptables -I DOCKER-USER 1 -s "$SBX" -j "$CHAIN"
iptables -C INPUT -s "$SBX" -m conntrack --ctstate NEW -j DROP 2>/dev/null \
  || iptables -I INPUT 1 -s "$SBX" -m conntrack --ctstate NEW -j DROP
EOF
chmod 0755 /usr/local/sbin/chathub-sbx-firewall.sh

cat > /etc/systemd/system/chathub-sbx-firewall.service <<'EOF'
[Unit]
Description=Firewall rules for the ChatHub OpenSandbox network
After=docker.service
PartOf=docker.service

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/local/sbin/chathub-sbx-firewall.sh

[Install]
WantedBy=docker.service
EOF

systemctl daemon-reload
systemctl enable --now chathub-sbx-firewall.service

2. Build the sandbox image

Build docker/opensandbox from this repository on the host that runs OpenSandbox. The tag below is what ChatHub sends as OPENSANDBOX_IMAGE.

docker build -t chathub-sandbox:1 docker/opensandbox
docker run --rm chathub-sandbox:1 python3 -c "import pandas, matplotlib, reportlab; print('image ok')"

The image is python:3.12-slim plus Node.js (npm, pnpm, yarn), git, and common CLIs (curl, jq, ripgrep, sqlite3, zip). It includes numpy, pandas, matplotlib, and the office libraries the prompt prefers when they are installed, plus a STSong.ttf path for Chinese PDFs. The official opensandbox/code-interpreter image is not this image.

To add a package, edit docker/opensandbox/requirements.txt (or the apt list in the Dockerfile) and rebuild. A pip install inside a call is not a substitute: it disappears when that sandbox is removed.

The image must already be on the host. ChatHub does not send registry credentials.

3. Start the OpenSandbox server

Tested server images: opensandbox/server:release-1.1.0 and release-1.1.1-rc.1. execd image: opensandbox/execd:v1.1.0. publish_host needs 1.1.1 or later so sandbox ports are not published on public interfaces.

docker pull opensandbox/execd:v1.1.0
mkdir -p ~/chathub/opensandbox

~/chathub/opensandbox/config.toml:

[server]
host = "0.0.0.0"
port = 8090
max_sandbox_timeout_seconds = 3600

[proxy]
resolve_internal = false

[runtime]
type = "docker"
execd_image = "opensandbox/execd:v1.1.0"

[docker]
network_mode = "chathub-sbx"
host_ip = "172.30.0.1"
publish_host = "172.30.0.1"
port_range_min = 40000
port_range_max = 41000
no_new_privileges = true
pids_limit = 4096

[ingress]
mode = "direct"

Server 1.1.0 defaults network_mode to host. Set it. Do not add a [secure_runtime] block if you want runc.

OPENSANDBOX_EGRESS_ALLOW (a deny-by-default domain list) needs [docker] network_mode = "bridge" and the egress sidecar. It is rejected on a user-defined network such as chathub-sbx. Leave the allowlist unset with the network in this guide, and rely on the firewall from step 1.

~/chathub/opensandbox.env (mode 600):

cd ~/chathub
[ -f opensandbox.env ] || { printf 'SANDBOX_CONFIG_PATH=/etc/opensandbox/config.toml\nOPENSANDBOX_SERVER_API_KEY=%s\n' "$(openssl rand -hex 32)" > opensandbox.env; chmod 600 opensandbox.env; }

Compose service. The data volume keeps renewed expiry across server recreates. Do not publish a port.

  opensandbox:
    image: opensandbox/server:release-1.1.1-rc.1
    container_name: chathub-opensandbox
    restart: always
    env_file:
      - opensandbox.env
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./opensandbox/config.toml:/etc/opensandbox/config.toml:ro
      - ./opensandbox/data:/root/.opensandbox
    networks:
      - 1panel-network
    security_opt:
      - no-new-privileges:true
docker compose up -d opensandbox
docker logs chathub-opensandbox 2>&1 | tail -3

Expect Uvicorn on http://0.0.0.0:8090.

max_sandbox_timeout_seconds applies only when a sandbox is created, not when ChatHub renews it. The default create lifetime is about 240 seconds (60s ready + 60s call + 120s). A call budget above 3420000 ms with the default ready budget makes that create longer than 3600 seconds and every create returns HTTP 400. Raise the server setting, or lower SANDBOX_TIMEOUT / SANDBOX_MAX_TIMEOUT.

4. Point ChatHub at the server

Put these in ChatHub's env file (lobe.env on 1Panel) or its environment: block. OPENSANDBOX_API_KEY must equal OPENSANDBOX_SERVER_API_KEY.

Variable Required Purpose
OPENSANDBOX_SERVER_URL yes http://chathub-opensandbox:8090 on the shared Docker network
OPENSANDBOX_API_KEY yes Same value as the server's OPENSANDBOX_SERVER_API_KEY
OPENSANDBOX_IMAGE yes chathub-sandbox:1
OPENSANDBOX_CPU / OPENSANDBOX_MEMORY no Defaults 1 and 2Gi
OPENSANDBOX_READY_TIMEOUT no Create plus execd ready, milliseconds (default 60000)
OPENSANDBOX_SESSION_IDLE_TIMEOUT no Keep the conversation's sandbox this long after the last call, milliseconds (default 1800000). 0 starts a fresh sandbox every call and disables background commands
OPENSANDBOX_SESSION_MAX_LIFETIME no Optional age cap in milliseconds (default 0, no cap)
SANDBOX_TIMEOUT no Default per-call limit, milliseconds (default 60000)
SANDBOX_MAX_TIMEOUT no Longest limit the model may request (default 600000)
SANDBOX_MAX_FILE_BYTES no Per-file cap (default 10 MiB)
SANDBOX_MAX_FILE_COUNT no Default 20
SANDBOX_MAX_OUTPUT_CHARS no stdout and stderr kept per call (default 30000)

If a SANDBOX_* limit is unset, ChatHub still reads the old name: CODE_INTERPRETER_TIMEOUT, CODE_INTERPRETER_MAX_FILE_BYTES, CODE_INTERPRETER_MAX_FILE_COUNT, or CODE_INTERPRETER_MAX_STDOUT_CHARS.

Recreate the ChatHub container after editing the env file. Enable Sandbox in the assistant tool picker.

Ordinary chat messages do not restart the idle timer. Only Sandbox calls do. Stop during a call cancels the turn. It does not leave a failed tool result for the model to continue from.

Upgrade from the Dify sidecar

Remove the code-interpreter service and its depends_on entry in the same edit. Compose will not start ChatHub if depends_on names a service that is gone.

Delete SANDBOX_PROVIDER, CODE_INTERPRETER_SANDBOX_URL, and CODE_INTERPRETER_SANDBOX_API_KEY. Set the OPENSANDBOX_* variables above. There is no provider switch and no rollback flag.

Troubleshooting

What you see What to do
Tool says the sandbox is not configured Set both OPENSANDBOX_SERVER_URL and OPENSANDBOX_IMAGE, then recreate ChatHub
HTTP 401 from the server OPENSANDBOX_API_KEY does not match OPENSANDBOX_SERVER_API_KEY
HTTP 400 on create, mentioning the maximum timeout Create TTL is over max_sandbox_timeout_seconds. Lower SANDBOX_TIMEOUT / SANDBOX_MAX_TIMEOUT, or raise the server setting
Call timed out The command ran longer than SANDBOX_TIMEOUT (or the model's timeout, capped by SANDBOX_MAX_TIMEOUT)
ModuleNotFoundError after the chat went idle The sandbox was removed. Install again, raise OPENSANDBOX_SESSION_IDLE_TIMEOUT, or add the package to the image
Background command refused OPENSANDBOX_SESSION_IDLE_TIMEOUT=0 (a fresh sandbox every call)
Sandbox still listed after a successful call Expected until the idle timeout. sandbox_cleanup_failed means a delete or renew failed; the server TTL still reaps it

Clone this wiki locally