ClouisleClouisle

代码沙箱

在隔离的 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 过期清理。

支持的运行时

运行时基础环境
PythonPython 3.13,包含标准库和任务配置的依赖
JavaScriptNode.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=1sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
Debian / 旧内核用户命名空间克隆被禁用(kernel.unprivileged_userns_clone=0sysctl -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=trueSANDBOX_FILESYSTEM_ISOLATION_BINARY 指向 Bubblewrap 可执行文件(Compose/Helm 默认 /usr/bin/bwrap);找不到 bwrap 或任务没有工作空间根目录时任务直接失败,不会降级为未隔离执行。Docker Compose 和 Helm 中的 sandbox-worker 默认使用 root + CAP_SYS_ADMIN + seccomp=unconfined 的安全配置;部署时若出现 bwrap 用户命名空间错误,见 故障排查


参见:

这篇文章对你有帮助吗?

本页目录