代码沙箱
在隔离的 Bubblewrap 命名空间中安全运行 Python 与 JavaScript 代码
代码沙箱是 Clouisle 的安全隔离执行环境,用于在工作流、Agent 和工具中运行用户提供的 Python 与 JavaScript 代码。沙箱任务进入专用 Celery Worker,由 rootless Bubblewrap 提供文件系统隔离,并通过超时、输出限制和磁盘检查约束失控任务。
隔离架构
Agent/工作流 → API → Celery 队列 (sandbox) → 沙箱 Worker
↓
Bubblewrap 进程
↓
/workspace → 当前任务/会话目录- 真实
/workspace路径:当前任务或会话目录以读写方式 bind mount 到/workspace,Python、Node.js、原生库和子进程看到相同路径。 - 文件系统隔离:任务命名空间不挂载其他会话、
/app或/app/uploads;系统运行时目录和依赖缓存只读挂载。 - 进程生命周期隔离:每次执行使用独立进程组,超时终止整个进程组。
- 路径防护:输入暂存、文件工具和产物收集拒绝逃逸工作空间及符号链接穿越。
- 根目录扫描收敛:Agent 直接执行的
find /会转换为find /workspace;脚本自行启动的命令也只能看到 Bubblewrap 暴露的最小文件系统。 - 自动清理:一次性任务执行完毕立即清理;会话按 TTL 过期清理。
支持的运行时
| 运行时 | 基础环境 |
|---|---|
| Python | Python 3.13,包含标准库和任务配置的依赖 |
| JavaScript | Node.js 22,包含核心模块和任务配置的依赖 |
使用场景
- 代码工具:在 管理后台 > 功能 > 代码 中创建可复用的代码工具,可安装精确固定版本的包并声明
/workspace下的产物,保存后被 Agent 和工作流调用(见 自定义 HTTP 与代码工具)。 - 工作流代码节点:在工作流图中直接嵌入代码,接收输入变量并将结果返回给下游节点(见 工作流节点)。
- Agent 级执行:Agent 通过函数调用触发代码工具,由 LLM 根据任务需要决定何时运行代码。
执行边界
代码工具默认超时 30 秒,持久化配置允许 1-600 秒;直接执行接口限制 1-60 秒。默认磁盘 1024MB,标准输出和错误各 256KB。产物路径必须位于 /workspace。边界明细见 工具与 Skills 的沙箱边界。
安全模型
- 任务负载在全新的 Bubblewrap 用户+挂载命名空间内执行,绝不会运行在 Worker 自身的命名空间里。
- 项目提供的部署让 Sandbox Worker 以 root 运行,并在运行时默认 cap 集上叠加
CAP_SYS_ADMIN:镜像的非 root 用户 effective capabilities 恒为空,而特权 Worker 即使在宿主禁止非特权用户命名空间时也能创建用户命名空间。Worker 保持allowPrivilegeEscalation=false,且仅对 sandbox-worker 使用seccomp=unconfined。 - 禁止
seccomp=unconfined的集群需要提供允许 namespace/mount 系统调用的 Localhost seccomp profile。 - 任务命名空间内只有当前工作空间及其临时目录可写;依赖缓存和必要运行时目录只读挂载。
- 子进程只接收过滤后的环境变量,而不是 Worker 的完整进程环境。
- 会话过期后自动清理工作目录。
宿主内核要求
Bubblewrap 通过 unshare(CLONE_NEWUSER) 创建新的用户命名空间。项目提供的部署(Docker Compose、Helm、Kubernetes)让 Worker 以 root + CAP_SYS_ADMIN 运行,用户命名空间创建走特权路径,即使宿主限制非特权用户命名空间也能正常工作——无需修改宿主 sysctl。
自定义部署若保持 Worker 非 root,则依赖宿主内核允许非特权用户命名空间,否则所有沙箱任务都会失败,报错为:
bwrap: No permissions to create new namespace, likely because the kernel does not allow non-privileged user namespaces.部分常见宿主发行版默认限制该能力,且 seccomp=unconfined 无法解决——限制发生在容器 seccomp profile 更底层的宿主内核:
| 发行版 | 限制 | 修复 |
|---|---|---|
| Ubuntu 23.10+ | AppArmor 禁止非特权进程创建用户命名空间(kernel.apparmor_restrict_unprivileged_userns=1) | sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 |
| Debian / 旧内核 | 用户命名空间克隆被禁用(kernel.unprivileged_userns_clone=0) | sysctl -w kernel.unprivileged_userns_clone=1 |
检查当前状态并验证用户命名空间确实可创建:
sysctl kernel.apparmor_restrict_unprivileged_userns kernel.unprivileged_userns_clone 2>/dev/null
unshare -U true && echo "user namespaces OK"使修改持久化:
echo 'kernel.apparmor_restrict_unprivileged_userns=0' > /etc/sysctl.d/99-clouisle-userns.conf
sysctl --system注意事项:
- sysctl 是宿主/节点级设置。在 Kubernetes 中无法按 Pod 设置:需要对每个节点生效(自管节点写入
/etc/sysctl.d/,托管集群使用自定义节点镜像或等效的节点初始化配置)。 - 允许非特权用户命名空间是 rootless 容器(Bubblewrap、Flatpak、Podman)的标准前提。
加固:收敛 Worker 的 CAP_SYS_ADMIN
沙箱任务运行在全新的 Bubblewrap 用户+挂载命名空间内,无法直接触及 Worker 容器的 capabilities。但如果任务成功逃出 Bubblewrap,落点就是容器内 root + CAP_SYS_ADMIN。默认 Docker daemon 下容器与宿主共享初始用户命名空间,该 capability 是宿主 userns 级别的,经典逃逸链(cgroup release_agent、重挂 /proc 写入 kernel.core_pattern、sysctl 写入)在原理上可达。
- Docker Compose:启用 daemon 用户命名空间重映射(
/etc/docker/daemon.json中"userns-remap": "default"),让每个容器进入嵌套用户命名空间——CAP_SYS_ADMIN只作用于容器自身的 userns,宿主逃逸链全部失效。沙箱 Worker 仍能在重映射后的命名空间内特权创建自己的 Bubblewrap userns,沙箱功能不受影响。启用后需重设既有命名卷属主,且 daemon 上所有容器都会被重映射。 - Kubernetes:不适用 daemon 级重映射;依赖 NetworkPolicy 限制沙箱出网、及时升级 Bubblewrap 并关注其 CVE,或在集群提供节点级用户命名空间支持时使用该能力。
相关配置
沙箱环境变量集中在 环境变量参考。启用文件系统隔离需要 SANDBOX_FILESYSTEM_ISOLATION_ENABLED=true 且 SANDBOX_FILESYSTEM_ISOLATION_BINARY 指向 Bubblewrap 可执行文件(Compose/Helm 默认 /usr/bin/bwrap);找不到 bwrap 或任务没有工作空间根目录时任务直接失败,不会降级为未隔离执行。Docker Compose 和 Helm 中的 sandbox-worker 默认使用 root + CAP_SYS_ADMIN + seccomp=unconfined 的安全配置;部署时若出现 bwrap 用户命名空间错误,见 故障排查。
参见:
- 工具与 Skills — 内置工具与沙箱边界
- 自定义 HTTP 与代码工具 — 配置代码工具
- 工作流节点 — 代码节点集成
- 环境变量参考 — Sandbox 环境变量
这篇文章对你有帮助吗?