GLM-5.2沙盒方案

作者:Administrator 发布时间: 2026-06-16 阅读量:2 评论数:0

MaxKB 沙盒方案选型与改造报告

一、MaxKB 当前沙盒方案调研

1.1 当前沙盒架构

MaxKB 当前采用的是 三层隔离方案,而非 Docker 容器:

层面技术核心文件
C 拦截层LD_PRELOAD 注入 sandbox.so,拦截危险系统调用installer/sandbox.c
用户隔离层setuid/setgid 降权到 sandbox 用户installer/Dockerfile-base
Python 资源限制层RLIMIT_AS 内存限制 + sched_setaffinity CPU 限制 + 超时控制apps/common/utils/tool_code.py

所有用户代码(工具、技能、本地 MCP)都在 MaxKB 同一进程/容器内,以 sandbox 用户身份通过子进程执行。

1.2 核心代码入口

  • 工具/工具库代码执行: ToolExecutor.exec_code()apps/common/utils/tool_code.py:101
  • 底层子进程执行: ToolExecutor._exec()apps/common/utils/tool_code.py:351
    • 注入 LD_PRELOAD=sandbox.so,通过 preexec_fn 设置资源限制
  • 本地 MCP Server 生成: ToolExecutor.get_tool_mcp_config()apps/common/utils/tool_code.py:323
    • 将自定义 Python 工具动态转成 FastMCP server,通过 stdio 通信,同样注入 sandbox.so
  • Deep Agent Shell 执行: SandboxShellBackend.execute()apps/application/flow/backend/sandbox_shell.py:56
    • gosu sandbox 切换用户 + 注入 sandbox.so
  • 技能运行: ZIP 解压到 /tmp/<chat_id>/skills/,由 deep agent 在 SandboxShellBackend 中通过 shell 访问
  • 配置项: apps/maxkb/conf.py,通过 MAXKB_ 前缀环境变量配置(如 MAXKB_SANDBOX=1

1.3 当前方案的局限性

问题说明
无法运行二进制文件sandbox.so 默认拦截 execve/mmap(PROT_EXEC)/mprotect(PROT_EXEC),二进制 skill 无法运行
隔离强度弱LD_PRELOAD 属于用户态 hook,可通过静态链接、直接 syscall、/proc/self/mem 等方式绕过
共享内核所有沙盒进程与宿主共享同一 Linux 内核,无内核级隔离
沙盒可被禁用is_sandbox_user() 检查可被同名用户欺骗;.sandbox.conf 可被运行时修改
资源限制有限仅内存/CPU/超时,无文件系统隔离(依赖用户权限),无网络命名空间
仅支持 Python工具代码通过 exec() 执行 Python,不支持其他语言

二、候选沙盒方案对比

2.1 六个方案总览

方案隔离机制二进制支持Python SDK部署复杂度本地/远程
Clampdown容器(Landlock+Seccomp)有(exec白名单)中(需Podman+Linux 6.2+)本地CLI
Daytona微VM(独立内核)完全支持有(完善)低(托管)~高(自建)远程API
E2B微VM(Firecracker)完全支持有(完善)低(托管)~极高(自建仅AWS/GCP)远程API
Firecracker微VM(KVM裸金属)间接(需自备kernel+rootfs)极高本地(底层)
Firecracker-containerd容器+微VM完全支持(OCI)高(需特殊containerd)本地(底层)
Microsandbox微VM(libkrun)完全支持(OCI)有(PyO3)低(一行安装)本地嵌入式

2.2 按需求维度评估

需求 1:支持运行二进制文件

  • 通过:Daytona、E2B、Firecracker-containerd、Microsandbox(完整 VM/OCI 镜像,任意二进制可运行)
  • 受限通过:Clampdown(需 SHA-256 白名单)、Firecracker(需自备 rootfs)
  • 不满足:无

需求 2:安全隔离性强(优于 LD_PRELOAD)

  • 最强:Firecracker、Firecracker-containerd、Microsandbox、Daytona、E2B(硬件级微 VM 隔离,独立内核)
  • 强:Clampdown(Landlock+Seccomp,仍共享内核但加固程度高)
  • 当前方案:LD_PRELOAD(最弱)

需求 3:改造难度低(有 Python SDK 或 HTTP API)

  • 最佳:Daytona、E2B、Microsandbox(均有成熟 Python SDK)
  • 无 SDK:Clampdown、Firecracker、Firecracker-containerd(需自行封装调用层)

需求 4:部署简便

  • 最简便:Microsandbox(curl | sh 一行安装,无 daemon,无服务器)
  • 简便(托管模式):Daytona、E2B(注册即用,但需联网)
  • 复杂:Firecracker、Firecracker-containerd、Clampdown

三、推荐方案:Microsandbox(首选)+ Daytona(远程备选)

3.1 推荐理由

首选 Microsandbox,理由如下:

  1. 本地嵌入式,架构契合度高:Microsandbox 以子进程方式启动微 VM,无需长期运行的 daemon 或服务器,与 MaxKB 当前的 subprocess.run() 调用模式天然契合。
  2. Python SDK 直接可用:通过 pip install microsandbox 安装,在 MaxKB 代码中直接 from microsandbox import Sandbox 即可调用。
  3. 部署极简:一行命令安装运行时,首次使用自动拉取 OCI 镜像并缓存,无需运维 Docker/Kubernetes。
  4. 硬件级隔离:基于 libkrun 的微 VM,每个沙盒有独立 Linux 内核,安全隔离性远超 LD_PRELOAD。
  5. 完整二进制支持:通过 OCI 镜像运行任意二进制文件,完美满足不开源 skill 的运行需求。
  6. 毫秒级启动:平均启动时间 < 100ms,对用户体验影响极小。
  7. 网络安全内置:默认阻止私有 IP、云元数据端点,DNS rebinding 防护,无需额外配置。
  8. License 友好:Apache-2.0。

备选 Daytona(适用于需要远程/云端部署的场景):

  1. 完善的 REST API + 多语言 SDK:适合 MaxKB 通过远程调用方式使用。
  2. 托管服务可用:注册即用,零运维成本。
  3. 功能最全面:支持快照、卷、SSH、Web Terminal、LSP 等。
  4. 适合规模化部署:有完整的 Control Plane / Compute Plane 架构。

3.2 各方案淘汰原因

方案淘汰原因
Clampdown无 Python SDK/HTTP API,纯 CLI 工具;专为 Claude Code/Codex 等 AI 编码代理设计,与 MaxKB 场景不匹配;需 Linux 6.2+ 内核
E2B自托管仅支持 AWS/GCP,不支持普通 Linux 机器;自建部署复杂度极高(需 Terraform 编排基础设施)
Firecracker纯底层 VMM,无 SDK,需自行准备 kernel+rootfs,面向基础设施开发者而非应用开发者
Firecracker-containerd无 Python SDK,需编译特殊版 containerd,部署复杂度高;项目更新节奏慢

四、MaxKB 改造方案

4.1 整体架构

改造前:
┌─────────────────────────────────────────────────┐
│                 MaxKB 容器                        │
│  ┌──────────┐   ┌──────────────────────────┐     │
│  │ MaxKB    │   │ subprocess (python)       │     │
│  │ 主进程   │──▶│ LD_PRELOAD=sandbox.so     │     │
│  │          │   │ setuid(sandbox)           │     │
│  └──────────┘   └──────────────────────────┘     │
│                  共享内核,用户态隔离               │
└─────────────────────────────────────────────────┘

改造后(Microsandbox 方案):
┌─────────────────────────────────────────────────┐
│                 MaxKB 主机                        │
│  ┌──────────┐   ┌──────────────────────────┐     │
│  │ MaxKB    │   │ Microsandbox 微 VM        │     │
│  │ 主进程   │──▶│ (独立内核, libkrun)        │     │
│  │ Python   │   │ ┌────────────────────┐   │     │
│  │ SDK      │   │ │ OCI 镜像环境        │   │     │
│  │          │   │ │ Python/二进制/MCP  │   │     │
│  └──────────┘   │ └────────────────────┘   │     │
│                  └──────────────────────────┘     │
│                  硬件级隔离,独立内核               │
└─────────────────────────────────────────────────┘

4.2 配置层改造

apps/maxkb/conf.py 中新增沙盒后端配置项:

# 新增配置项(通过 MAXKB_ 前缀环境变量覆盖)
SANDBOX_BACKEND = CONFIG.get("SANDBOX_BACKEND", "legacy")  
# 可选值: "legacy"(原sandbox.so) | "microsandbox" | "daytona"

# Microsandbox 配置
MICROSANDBOX_IMAGE = CONFIG.get("MICROSANDBOX_IMAGE", "python:3.11-slim")
MICROSANDBOX_CPUS = int(CONFIG.get("MICROSANDBOX_CPUS", "1"))
MICROSANDBOX_MEMORY = int(CONFIG.get("MICROSANDBOX_MEMORY", "512"))  # MB
MICROSANDBOX_TIMEOUT = int(CONFIG.get("MICROSANDBOX_TIMEOUT", "3600"))

# Daytona 配置(远程模式)
DAYTONA_API_KEY = CONFIG.get("DAYTONA_API_KEY", "")
DAYTONA_API_URL = CONFIG.get("DAYTONA_API_URL", "https://app.daytona.io/api")
DAYTONA_TIMEOUT = int(CONFIG.get("DAYTONA_TIMEOUT", "3600"))

4.3 新增沙盒后端抽象层

新建 apps/common/utils/sandbox_backend.py,定义统一的沙盒后端接口:

from abc import ABC, abstractmethod
from dataclasses import dataclass


@dataclass
class ExecResult:
    returncode: int
    stdout: str
    stderr: str


class SandboxBackend(ABC):
    """沙盒后端抽象基类"""

    @abstractmethod
    def exec_code(self, code: str, function_name: str, params: dict) -> dict:
        """执行 Python 代码并调用指定函数,返回结果"""
        ...

    @abstractmethod
    def exec_command(self, command: str, timeout: int = None) -> ExecResult:
        """在沙盒中执行 shell 命令"""
        ...

    @abstractmethod
    def upload_files(self, local_path: str, remote_path: str) -> None:
        """上传文件到沙盒(用于 skill 文件部署)"""
        ...

    @abstractmethod
    def cleanup(self) -> None:
        """清理沙盒资源"""
        ...

4.4 实现 Microsandbox 后端

新建 apps/common/utils/sandbox_microsandbox.py

import asyncio
import json
import os
import tempfile
from common.utils.sandbox_backend import SandboxBackend, ExecResult
from common.utils.logger import maxkb_logger
from maxkb.const import CONFIG


class MicrosandboxBackend(SandboxBackend):
    def __init__(self):
        from microsandbox import Sandbox
        self._Sandbox = Sandbox
        self._image = CONFIG.get("MICROSANDBOX_IMAGE", "python:3.11-slim")
        self._cpus = int(CONFIG.get("MICROSANDBOX_CPUS", "1"))
        self._memory = int(CONFIG.get("MICROSANDBOX_MEMORY", "512"))
        self._timeout = int(CONFIG.get("MICROSANDBOX_TIMEOUT", "3600"))
        self._sandbox = None

    async def _ensure_sandbox(self):
        """延迟创建沙盒实例(每个执行上下文一个)"""
        if self._sandbox is None:
            self._sandbox = await self._Sandbox.create(
                name=f"maxkb-{os.getpid()}",
                image=self._image,
                cpus=self._cpus,
                memory=self._memory,
            )
        return self._sandbox

    async def exec_code(self, code, function_name, params):
        """在微 VM 中执行 Python 代码"""
        sandbox = await self._ensure_sandbox()
        # 将代码和参数写入临时文件,在沙盒中执行
        wrapper_code = self._wrap_code(code, function_name, params)
        result = await sandbox.exec(
            "python3", ["-c", wrapper_code],
            timeout=self._timeout
        )
        return json.loads(result.stdout_text)

    async def exec_command(self, command, timeout=None):
        """在微 VM 中执行 shell 命令"""
        sandbox = await self._ensure_sandbox()
        result = await sandbox.exec(
            "sh", ["-c", command],
            timeout=timeout or self._timeout
        )
        return ExecResult(
            returncode=result.exit_code,
            stdout=result.stdout_text,
            stderr=result.stderr_text,
        )

    async def upload_files(self, local_path, remote_path):
        """上传文件到微 VM(部署 skill 文件)"""
        sandbox = await self._ensure_sandbox()
        await sandbox.fs.upload(local_path, remote_path)

    async def cleanup(self):
        """销毁微 VM"""
        if self._sandbox:
            await self._sandbox.stop()
            self._sandbox = None

    def _wrap_code(self, code, function_name, params):
        """生成包装代码(与原 exec_code 逻辑类似,但不做 setuid/sandbox.so)"""
        return f"""
import json, sys
params = json.loads('''{json.dumps(params)}''')
_locals = {{}}
exec('''{code}''', {{"__builtins__": __builtins__}}, _locals)
result = _locals["{function_name}"](**params)
json.dump({{"code": 200, "msg": "success", "data": result}}, sys.stdout, default=str)
"""

4.5 改造 ToolExecutor(核心改造点)

修改 apps/common/utils/tool_code.py,根据配置选择沙盒后端:

# 文件顶部新增
from common.utils.sandbox_backend import SandboxBackend

_sandbox_backend_type = CONFIG.get("SANDBOX_BACKEND", "legacy")

def _get_sandbox_backend() -> SandboxBackend:
    """根据配置返回沙盒后端实例"""
    if _sandbox_backend_type == "microsandbox":
        from common.utils.sandbox_microsandbox import MicrosandboxBackend
        return MicrosandboxBackend()
    elif _sandbox_backend_type == "daytona":
        from common.utils.sandbox_daytona import DaytonaBackend
        return DaytonaBackend()
    else:
        return None  # 使用原有 legacy 方案

修改 exec_code() 方法(第 101-155 行),当后端为微 VM 时走新路径:

class ToolExecutor:
    # ...

    def exec_code(self, code_str, keywords, function_name=None):
        # 新增:如果使用微 VM 沙盒,走新执行路径
        if _sandbox_backend_type in ("microsandbox", "daytona"):
            return self._exec_code_in_microvm(code_str, keywords, function_name)
        
        # ---- 以下为原有 legacy 逻辑,保持不变 ----
        _id = str(uuid.uuid7())
        # ... 原有代码 ...

    def _exec_code_in_microvm(self, code_str, keywords, function_name):
        """在微 VM 沙盒中执行代码"""
        import asyncio
        backend = _get_sandbox_backend()
        try:
            result = asyncio.get_event_loop().run_until_complete(
                backend.exec_code(code_str, function_name, keywords)
            )
            if result.get("code") == 200:
                return result.get("data")
            raise Exception(result.get("msg"))
        finally:
            asyncio.get_event_loop().run_until_complete(backend.cleanup())

4.6 改造 SandboxShellBackend(Deep Agent Shell 执行)

修改 apps/application/flow/backend/sandbox_shell.py,当使用微 VM 时,将 shell 命令转发到沙盒中执行:

class SandboxShellBackend(LocalShellBackend):
    def __init__(self, root_dir: str, **kwargs):
        self._use_microvm = _sandbox_backend_type in ("microsandbox", "daytona")
        if self._use_microvm:
            # 微 VM 模式下不需要 LD_PRELOAD 和 gosu,隔离由微 VM 保证
            kwargs.setdefault("env", {})
        else:
            # 原有逻辑
            ...
        super().__init__(root_dir=root_dir, **kwargs)

    def execute(self, command, *, timeout=None):
        if self._use_microvm:
            return self._execute_in_microvm(command, timeout)
        
        # ---- 以下为原有 legacy 逻辑 ----
        if self.virtual_mode:
            command = self._translate_virtual_paths(command)
        if _enable_sandbox:
            command = (
                "env -i LD_PRELOAD=/opt/maxkb-app/sandbox/lib/sandbox.so "
                f'PATH="${{PATH}}" PYTHONPATH="${{PYTHONPATH}}" gosu {_run_user} {command}'
            )
        return super().execute(command=command, timeout=timeout)

    def _execute_in_microvm(self, command, timeout):
        """在微 VM 中执行 shell 命令"""
        import asyncio
        from common.utils.sandbox_microsandbox import MicrosandboxBackend
        backend = MicrosandboxBackend()
        try:
            result = asyncio.get_event_loop().run_until_complete(
                backend.exec_command(command, timeout)
            )
            from deepagents.backends.protocol import ExecuteResponse
            return ExecuteResponse(
                stdout=result.stdout,
                stderr=result.stderr,
                exit_code=result.returncode,
            )
        finally:
            asyncio.get_event_loop().run_until_complete(backend.cleanup())

4.7 改造本地 MCP Server 生成逻辑

修改 get_tool_mcp_config()tool_code.py:323),当使用微 VM 时,MCP server 在微 VM 内以网络端口方式运行,而非 stdio:

def get_tool_mcp_config(self, tool, params):
    if _sandbox_backend_type in ("microsandbox", "daytona"):
        return self._get_tool_mcp_config_microvm(tool, params)
    
    # ---- 以下为原有 legacy 逻辑 ----
    _code = self.generate_mcp_server_code(tool.code, params, tool.name, tool.desc, str(tool.id))
    # ... 原有代码 ...

def _get_tool_mcp_config_microvm(self, tool, params):
    """在微 VM 中运行 MCP server,暴露 HTTP 端点"""
    code = self._generate_mcp_server_code_http(tool.code, params, tool.name, tool.desc, str(tool.id))
    # 生成在微 VM 内以 streamable_http 方式运行的 MCP server 代码
    # 返回指向微 VM 的 HTTP MCP 配置
    import asyncio
    backend = _get_sandbox_backend()
    # 在微 VM 中启动 MCP server,获取端口映射
    port = asyncio.get_event_loop().run_until_complete(
        backend.start_mcp_server(code)
    )
    return {
        "url": f"http://127.0.0.1:{port}/mcp",
        "transport": "streamable_http",
    }

同时需修改 _generate_mcp_server_code(),将 transport="stdio" 改为 transport="streamable_http" 并绑定到 0.0.0.0

4.8 改造 Skill 运行逻辑

修改 application/flow/tools.py 中的 _initialize_skills()(第 398-441 行):

async def _initialize_skills(mcp_servers, temp_dir):
    skills_dir = os.path.join(temp_dir, "skills")
    mcp_config = json.loads(mcp_servers)
    
    if _sandbox_backend_type in ("microsandbox", "daytona"):
        # 微 VM 模式:将 skill 文件上传到微 VM 中
        if "skills" in mcp_config:
            skill_file_items = mcp_config.pop("skills")
            backend = _get_sandbox_backend()
            for skill_file in skill_file_items:
                file = await QuerySet(File).filter(id=skill_file["file_id"]).first()
                file_bytes = await file.get_bytes()
                # 写入本地临时文件后上传到微 VM
                local_zip = os.path.join(temp_dir, f"{skill_file['file_id']}.zip")
                with open(local_zip, "wb") as f:
                    f.write(file_bytes)
                await backend.upload_files(local_zip, f"/skills/{skill_file['file_id']}.zip")
                # 在微 VM 内解压
                await backend.exec_command(f"cd /skills && unzip {skill_file['file_id']}.zip")
            client = MultiServerMCPClient(mcp_config)
            return client, backend
    
    # ---- 以下为原有 legacy 逻辑 ----
    # ... 原有解压逻辑 ...

4.9 Dockerfile 改造

修改 installer/Dockerfile,新增 Microsandbox 运行时安装步骤:

# 安装 Microsandbox 运行时(如果启用微 VM 沙盒)
RUN if [ "$MAXKB_SANDBOX_BACKEND" = "microsandbox" ]; then \
        curl -fsSL https://install.microsandbox.dev | sh && \
        pip install microsandbox; \
    fi

# 保留原有 sandbox.so 编译(向后兼容)
RUN gcc -shared -fPIC -o ${MAXKB_SANDBOX_HOME}/lib/sandbox.so /opt/maxkb-app/installer/sandbox.c -ldl

同时需要在 Docker 运行参数中添加 KVM 设备映射(微 VM 需要 KVM 支持):

docker run --device /dev/kvm -e MAXKB_SANDBOX_BACKEND=microsandbox ...

4.10 改造文件清单

文件改造内容改动量
apps/maxkb/conf.py新增 SANDBOX_BACKEND 等配置项
apps/common/utils/sandbox_backend.py新建 沙盒后端抽象接口
apps/common/utils/sandbox_microsandbox.py新建 Microsandbox 后端实现
apps/common/utils/sandbox_daytona.py新建 Daytona 远程后端实现(可选)
apps/common/utils/tool_code.py改造 exec_code()/_exec()/get_tool_mcp_config(),根据后端类型分支
apps/application/flow/backend/sandbox_shell.py改造 execute(),转发命令到微 VM
apps/application/flow/tools.py改造 _initialize_skills(),文件上传到微 VM
installer/Dockerfile新增 Microsandbox 运行时安装
installer/Dockerfile-base条件化 sandbox 用户创建
pyproject.toml新增 microsandbox 依赖

五、改造实施计划

阶段一:基础设施搭建(1-2 周)

  1. 新建 sandbox_backend.py 抽象接口和 sandbox_microsandbox.py 实现
  2. conf.py 中新增配置项
  3. 编写 Microsandbox 后端的单元测试

阶段二:核心执行路径改造(1-2 周)

  1. 改造 ToolExecutor.exec_code() — Python 工具代码执行
  2. 改造 SandboxShellBackend.execute() — Deep Agent shell 执行
  3. 改造 get_tool_mcp_config() — 本地 MCP server
  4. 改造 _initialize_skills() — Skill 文件部署

阶段三:Dockerfile 和部署改造(3-5 天)

  1. 修改 Dockerfile,支持条件安装 Microsandbox 运行时
  2. 添加 KVM 设备映射到 docker-compose / 部署文档
  3. 准备预构建 OCI 镜像(包含常用 Python 包)

阶段四:测试与验证(1 周)

  1. 功能测试:工具执行、MCP 调用、Skill 运行、二进制 skill 运行
  2. 安全测试:网络隔离验证、文件系统隔离验证、逃逸尝试
  3. 性能测试:启动延迟、执行性能对比

阶段五:Daytona 远程模式(可选,1-2 周)

  1. 实现 sandbox_daytona.py 远程后端
  2. 添加远程 MCP 配置支持
  3. 测试云端托管模式

六、风险与注意事项

风险影响缓解措施
KVM 不可用(某些云环境/虚拟机)微 VM 无法启动检测 KVM 可用性,自动回退到 legacy 模式;或使用 Daytona 远程模式
Microsandbox 仍是 beta (v0.5.7)稳定性风险保留 legacy 方案作为回退选项;SANDBOX_BACKEND=legacy 可随时切回
微 VM 启动延迟首次请求变慢(~100ms)使用沙盒池预创建微 VM 实例;或保持长连接复用
OCI 镜像体积部署变慢预构建精简镜像(python:3.11-slim + 常用包),分层缓存
stdio MCP → HTTP MCP 改造兼容性确保 MultiServerMCPClient 支持 streamable_http(已支持)
Mac 开发环境无 KVM本地开发受阻Microsandbox 支持 macOS Apple Silicon(基于 libkrun)

七、结论

推荐 Microsandbox 作为 MaxKB 的下一代沙盒方案:

  • 安全隔离性:从 LD_PRELOAD 用户态隔离升级为 libkrun 硬件级微 VM 隔离(独立内核),安全性质变提升
  • 二进制支持:通过 OCI 镜像完整支持运行任意二进制文件,满足不开源 skill 的需求
  • 改造难度:有原生 Python SDK,核心改造集中在 ToolExecutorSandboxShellBackend 两个入口,改动可控
  • 部署友好:一行安装,无 daemon,对现有 Docker 部署仅增加 KVM 设备映射
  • 向后兼容:通过 SANDBOX_BACKEND 配置开关,可随时在 legacy 和 microsandbox 之间切换

如需远程/云端部署能力,可额外集成 Daytona 作为远程沙盒后端,二者共用统一的 SandboxBackend 接口。

评论