Code Sandbox
Run Python and JavaScript code safely in isolated Bubblewrap namespaces
The code sandbox is Clouisle's secure, isolated execution environment for running user-supplied Python and JavaScript code within workflows, agents, and tools. Sandbox tasks are submitted to a dedicated Celery worker, which uses rootless Bubblewrap for filesystem isolation and constrains runaway jobs with timeouts, output limits, and disk checks.
Isolation Architecture
Agent/Workflow → API → Celery Queue (sandbox) → Sandbox Worker
↓
Bubblewrap process
↓
/workspace → current job/session directory- Real
/workspacepath: the current job or session directory is bind-mounted read-write at/workspace, so Python, Node.js, native libraries, and child processes use the same path. - Filesystem isolation: sibling workspaces,
/app, and/app/uploadsare not mounted into the task namespace. Required system runtime directories and the dependency cache are mounted read-only. - Process lifecycle isolation: each execution uses a new process group; timeout handling terminates the whole group.
- Path protection: input staging, file tools, and artifact collection reject workspace escapes and symlink traversal.
- Root-scan confinement: direct Agent commands such as
find /are normalized tofind /workspace; commands launched from code still see only the minimal Bubblewrap filesystem. - Automatic cleanup: one-off jobs are cleaned immediately after execution; sessions are cleaned on TTL expiry.
Supported Runtimes
| Runtime | Base Environment |
|---|---|
| Python | Python 3.13 with standard library and configured packages |
| JavaScript | Node.js 22 with core modules and configured packages |
Usage in the Platform
- Code tool: create reusable code utilities from Admin Console > Capabilities > Code. Tools can install exact-pinned packages and declare artifacts under
/workspace; saved tools can be called by agents and workflows (see Custom HTTP and Code Tools). - Workflow code node: embed code directly in workflow graphs. The code node receives input variables and returns results to downstream nodes (see Workflow Nodes).
- Agent-level execution: agents invoke code tools via function calling; the LLM decides when to run code based on the task.
Execution Limits
Code tools default to a 30 second timeout with 1-600 seconds allowed for persisted configuration; the direct execution API limits 1-60 seconds. Default disk is 1024MB with 256KB each for stdout and stderr. Artifact paths must live under /workspace. See Tools & Skills → Sandbox Boundaries for the full limits.
Security Model
- The task payload executes inside a fresh Bubblewrap user + mount namespace, never in the worker's own namespaces.
- The supplied deployments run the sandbox worker as root with
CAP_SYS_ADMINadded to the runtime default cap set: the image's non-root user has empty effective capabilities, and a privileged worker can create user namespaces even on hosts that gate unprivileged user namespaces. The worker keepsallowPrivilegeEscalation=falseandseccomp=unconfined(sandbox worker only). - Clusters that prohibit
seccomp=unconfinedmust provide a Localhost seccomp profile allowing the required namespace/mount syscalls. - Only the current workspace and its temporary directory are writable inside the task namespace; the dependency cache and required runtime directories are read-only.
- The child receives a filtered environment rather than the worker's full process environment.
- Session workspaces are cleaned after TTL expiry.
Host Kernel Requirements
Bubblewrap creates a new user namespace with unshare(CLONE_NEWUSER). The supplied deployments (Docker Compose, Helm, Kubernetes) run the worker as root with CAP_SYS_ADMIN, so user namespace creation is privileged and works even on hosts that restrict non-privileged user namespaces — no host sysctl changes are required.
Custom deployments that keep the worker non-root rely on the host kernel permitting unprivileged user namespaces; otherwise every sandbox job fails with:
bwrap: No permissions to create new namespace, likely because the kernel does not allow non-privileged user namespaces.Several common host distributions restrict this by default, and seccomp=unconfined does not help here because the restriction is enforced below the container seccomp profile:
| Distribution | Restriction | Fix |
|---|---|---|
| Ubuntu 23.10+ | AppArmor blocks user namespaces for unprivileged processes (kernel.apparmor_restrict_unprivileged_userns=1) | sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 |
| Debian / older kernels | User namespace cloning disabled (kernel.unprivileged_userns_clone=0) | sysctl -w kernel.unprivileged_userns_clone=1 |
Check the current state and verify that a user namespace can actually be created:
sysctl kernel.apparmor_restrict_unprivileged_userns kernel.unprivileged_userns_clone 2>/dev/null
unshare -U true && echo "user namespaces OK"Make the change persistent:
echo 'kernel.apparmor_restrict_unprivileged_userns=0' > /etc/sysctl.d/99-clouisle-userns.conf
sysctl --systemNotes:
- The sysctl is a host/node-level setting. In Kubernetes it cannot be set per pod: apply it to every node (custom node image,
/etc/sysctl.d/on self-managed nodes, or the equivalent node bootstrap for managed clusters). - Enabling unprivileged user namespaces is the standard prerequisite for rootless containers (Bubblewrap, Flatpak, Podman).
Hardening: Contain the Worker's CAP_SYS_ADMIN
The sandbox task runs in a fresh Bubblewrap user + mount namespace, so it cannot directly reach the worker container's capabilities. If a task ever escapes Bubblewrap, however, it lands inside the worker container as root with CAP_SYS_ADMIN. On a default Docker daemon the container shares the host's initial user namespace, which makes that capability host-user-namespace-scoped and exposes well-known escape chains (cgroup release_agent, remounting /proc to write kernel.core_pattern, sysctl writes) in principle.
- Docker Compose: enable daemon user namespace remapping (
"userns-remap": "default"in/etc/docker/daemon.json) to place every container in a nested user namespace —CAP_SYS_ADMINthen only applies to the container's own user namespace and the host-escape chains no longer work. The sandbox worker still creates its Bubblewrap user namespace (privileged inside the remapped namespace), so sandbox functionality is unaffected. Existing named volumes need an ownership reset, and all containers on the daemon are remapped. - Kubernetes: daemon-level remapping does not apply; rely on NetworkPolicy for outbound sandbox traffic, keep Bubblewrap updated and monitor its CVEs, or use node-level user namespace support where the cluster provides it.
Related Configuration
Sandbox environment variables are listed in the Environment Variables Reference. Enabling filesystem isolation requires SANDBOX_FILESYSTEM_ISOLATION_ENABLED=true with SANDBOX_FILESYSTEM_ISOLATION_BINARY pointing at the Bubblewrap executable (/usr/bin/bwrap by default in Compose/Helm); a missing binary or missing workspace root fails the task instead of falling back to direct execution. The sandbox-worker in Docker Compose and Helm defaults to the root + CAP_SYS_ADMIN + seccomp=unconfined security configuration; for bwrap user namespace errors during deployment, see Troubleshooting.
See also:
- Tools & Skills — built-in tools and sandbox boundaries
- Custom HTTP and Code Tools — configuring the code tool
- Workflow Nodes — code node integration
- Environment Variables Reference — sandbox environment variables
How is this guide?