Claude Code + CubeSandbox 集成实战:用 PreToolUse Hook 实现透明 MicroVM 隔离的 Bash 执行
【免费下载链接】CubeSandboxInstant, Concurrent, Secure & Lightweight Sandbox for AI Agents.项目地址: https://gitcode.com/GitHub_Trending/cu/CubeSandbox
导读
本文基于 CubeSandbox 仓库中的 claude-code-integration 示例,讲解如何让 Claude Code 继续运行在宿主机上,却把它发出的每一条 Bash 命令透明转发进隔离的 CubeSandbox MicroVM 执行:文件编辑留在本地、shell 命令跑在独立内核/文件系统/网络里,模型完全感知不到沙箱层,也不需要改变任何使用方式。读完本文,你将掌握 hook 的安装配置、改写与执行两个核心脚本的工作原理、按会话复用沙箱与 shell 状态持久化的机制、宿主项目只读挂载的约束,以及完整的安全设计要点和排错方法。
Claude Code(宿主机) ├── Read / Write / Edit ─────────────► 宿主机项目文件 │ └── Bash ──► PreToolUse hook ──► cubesandbox_exec ──► CubeAPI ──► MicroVM (cubesandbox_rewrite.py) (:3000) └─ 按会话复用为什么用 PreToolUse Hook 而不是 MCP / SDK
基于 MCP 或 SDK 的沙箱方案依赖 agent主动选择沙箱工具;一条普通的Bash调用仍会落到宿主机上。PreToolUsehook 堵住了这个缺口:它在工具调用执行之前拦截工具调用本身,因此对 Bash 而言隔离是透明且完整的——没有任何命令能绕过它。
| 特性 | 行为 |
|---|---|
| 透明 | 模型发起普通的Bash调用,由 hook 改写。无需改 prompt 或工具。 |
| 按会话复用沙箱 | 每个 Claude Codesession_id复用一个 MicroVM,同一会话的命令共享状态。 |
| shell 状态延续 | 同一会话内,cd和导出的环境变量在多次 Bash 调用间保留。 |
| 只读挂载宿主项目 | 会话首次调用可把项目按相同路径只读挂载,沙箱命令可读取(不可修改)宿主文件。 |
| fail-closed | 若无法安全改写,hook 以非零退出阻断命令,而不是放它到宿主机执行。 |
| 防注入 | 原命令作为单个shlex引用参数传入,shell 元字符和换行都无法越出到宿主机。 |
| 无条件改写 | 每条 Bash 调用都会被改写;已包裹的执行器调用若再次经过 hook,只会在沙箱内失败,绝不会落到宿主机。 |
| 自动批准 | hook 对改写后的 Bash 调用返回permissionDecision: "allow",Claude Code 的逐命令确认提示被抑制;请相应使用--permission-mode/ hooks 策略。 |
从源码结构看,
cubesandbox_rewrite.py对 payload 的改写逻辑在 examples/claude-code-integration/hooks/cubesandbox_rewrite.py 中实现,cubesandbox_exec.py承担执行与状态管理,二者通过install.sh注册进~/.claude/settings.json。
前置条件
- 运行中的 CubeSandbox 部署(CubeAPI 可达,如
http://127.0.0.1:3000) - 运行 Claude Code 的宿主机上有 Python 3.9+
- 一个用于创建沙箱的 CubeSandbox 模板(
cubemastercli tpl list可列出可用模板)
快速开始
1 —— 安装依赖并配置
python3 -m pip install -r requirements.txt cp .env.example .env # 编辑 .env:设置 CUBE_API_URL 和 CUBE_TEMPLATE_ID.env中的核心配置项及其说明(见 .env.example):
| 变量 | 说明 | 默认值 |
|---|---|---|
CUBE_API_URL | CubeAPI 的 base URL,SDK 通过它访问控制面 | http://127.0.0.1:3000 |
CUBE_TEMPLATE_ID | hook 创建沙箱使用的模板 ID(必填,缺失时执行器报CUBE_TEMPLATE_ID is not set) | 无 |
CUBE_SANDBOX_USER | 沙箱内执行命令的用户 | root |
CUBE_SANDBOX_TIMEOUT | 沙箱存活 TTL(秒) | 1800 |
CUBE_EXEC_TIMEOUT | 单条命令执行超时(秒) | 120 |
CUBE_HOOK_STATE_DIR | 会话→沙箱映射状态目录 | ~/.cache/cubesandbox-hook |
CUBE_PROXY_NODE_IP/CUBE_PROXY_PORT_HTTP/CUBE_SANDBOX_DOMAIN | 远程/自建部署下的 envd 路由覆盖,可选 | 空 |
其中CUBE_SANDBOX_USER、CUBE_SANDBOX_TIMEOUT、CUBE_EXEC_TIMEOUT、CUBE_HOOK_STATE_DIR在 cubesandbox_exec.py 中通过环境变量读取,且CUBE_SANDBOX_TIMEOUT/CUBE_EXEC_TIMEOUT必须是正整数/正有限浮点数,否则执行器以BootstrapError拒绝启动。
2 —— 安装 hook
cd hooks ./install.sh安装脚本(install.sh)会:
- 把
cubesandbox_rewrite.py与cubesandbox_exec.py复制到~/.claude/hooks/(权限 0755); - 将 hook 注册进
~/.claude/settings.json的hooks.PreToolUse下的Bashmatcher 分组,不覆盖你其它设置; - 只把
../.env里白名单内的CUBE_*值复制进 hook 配置cubesandbox.env(权限 0600),provider API key(如ANTHROPIC_AUTH_TOKEN、OPENAI_API_KEY)不会被写入 hook 配置。
重启 Claude Code 以加载 hook。
3 —— 照常使用 Claude Code
直接用 Claude Code。它发出的每条 Bash 命令现在都在 MicroVM 内执行:
> 执行 `uname -a && whoami`,告诉我它在哪运行命令在沙箱里运行(不同内核、沙箱用户),而 Claude Code 的文件编辑仍在你的宿主机上。
工作原理
1. 改写阶段:cubesandbox_rewrite.py
hook 接收PreToolUseJSON payload。对Bash调用,它把tool_input.command改写为:
<python> <hooks>/cubesandbox_exec.py --session=<id> --mount=<cwd> --timeout=<秒> -- <原命令>并通过updatedInput返回。关键实现细节(cubesandbox_rewrite.py):
- 原命令是单个引用参数:经
shlex.join引用,里面的 shell 元字符和换行无法在宿主机执行,--分隔符保证以-开头的命令不会被误解析为执行器参数; - session_id 复用:从 payload 的
session_id字段取值,缺失时回退到default; - 超时透传有校验:仅当
timeout是正有限数值且不超过MAX_TIMEOUT_MS(1 小时)时才追加--timeout,非法值(0、负数、nan、inf、字符串等)一律不转发; - fail-closed:payload 不是 JSON 对象、
tool_name缺失或tool_input非法时,hook 打印错误并以退出码 2阻断命令,绝不放行到宿主机; - 非 Bash 工具原样放行:
Read/Write/Edit等返回None,不产生任何改动; - 无条件改写:即使命令文本本身就是执行器调用(例如从 transcript 粘贴回来),也照常包裹——它只会在沙箱内失败(沙箱内不存在宿主 hook 路径),绝不会落到宿主机。
这些行为在 test_cubesandbox_rewrite.py 中有系统化验证,包括控制操作符保持为单个引用参数、session_id/cwd中的注入字符被隔离、非法 payload 被拒绝、超时边界校验,以及“仅命名执行器脚本也必须被包裹”等测试用例。
2. 执行阶段:cubesandbox_exec.py
执行器(cubesandbox_exec.py)按session_id复用一个沙箱:
- 会话映射状态存放在
~/.cache/cubesandbox-hook/(可用CUBE_HOOK_STATE_DIR覆盖),session_id 经 SHA-256 哈希作为文件名,状态目录权限 0700、状态文件权限 0600,并用每会话文件锁(fcntl.flock)保护并发; - 重用或新建:优先通过
Sandbox.connect(sandbox_id)重连已缓存的沙箱(失败自动回退新建),首次调用时通过Sandbox.create(TEMPLATE_ID, ...)创建并记录sandbox_id与随机state_token; - shell 状态延续:每条命令被包裹进一段状态包装脚本(
_state_shell)——恢复上次持久化的 cwd 与环境变量,运行原命令,再把pwd和export -p写回状态文件。其中BASH_ENV、ENV、LD_PRELOAD、PROMPT_COMMAND等会自执行恶意代码的变量被显式清洗; - 返回结果:命令结束后缓冲返回 stdout/stderr 与退出码(非流式),并将退出码透传给 Claude Code;
- 串行执行:同一会话内并发的 Bash 调用经每会话锁串行执行——一次只跑一条,不并行。
沙箱 SDK 侧的支撑:Sandbox与Config来自 sdk/python/cubesandbox/sandbox.py,Config的api_url/template_id等字段均可通过环境变量注入(见 sdk/python/cubesandbox/_config.py),与 hook 的.env白名单机制一一对应。
宿主项目挂载
会话的首次 Bash 调用可把 Claude Code 的项目目录按相同绝对路径只读挂载进沙箱——沙箱命令可读取宿主项目文件,但不能修改或往里写构建产物。挂载请求以host-mountmetadata 形式传入Sandbox.create(见 cubesandbox_exec.py):
[{"hostPath": "/path/to/project", "mountPath": "/path/to/project", "readOnly": true}]注意挂载的生效条件:hostPath在被调度到的 Cubelet 节点上解析,而非运行 Claude Code 的机器。因此只有当 Claude Code 与该 Cubelet 同机、或项目已经以相同绝对路径存在于每个可调度 Cubelet 上时,这种共享视图才成立。hook绝不会把本地项目上传或同步到远端部署——不要把一个仅客户端存在、在 Cubelet 上可能指向无关数据的路径加入白名单。
项目路径必须被 CubeMaster 允许:
extra_conf: allowed_host_mount_prefixes: - "/data/shared/" - "/home/you/projects/"若挂载被拒,执行会回退到无挂载的隔离沙箱:Bash 仍然隔离,但不再与 Claude Code 宿主侧文件工具共享文件视图。回退时会打印警告:
[cubesandbox-exec] warning: read-only host mount '/xxx' was rejected (...); creating a sandbox without the mount重置与卸载
# 丢弃某会话绑定的沙箱(下次调用重新创建) python3 ~/.claude/hooks/cubesandbox_exec.py --reset --session <session-id> # 从 ~/.claude/settings.json 移除 hook cd hooks ./install.sh --uninstall--reset会尽力 kill 已缓存的沙箱并清空该会话的状态(即使 API 不可达,状态也会被清除,避免会话被卡死),并回收累积的锁文件;卸载则从 settings.json 移除 Bash matcher 分组、删除已安装的 hook 文件,并保留~/.cache/cubesandbox-hook中的既有状态供用户自行处置。
目录结构
claude-code-integration/ ├── hooks/ │ ├── cubesandbox_rewrite.py # PreToolUse hook:把 Bash 改写为沙箱执行 │ ├── cubesandbox_exec.py # 执行器:按会话复用 MicroVM + 状态持久化 │ └── install.sh # 幂等安装 / 卸载 ├── tests/ │ ├── conftest.py │ ├── test_cubesandbox_rewrite.py │ ├── test_cubesandbox_exec.py │ └── test_hook_install.py ├── requirements.txt ├── .env.example ├── TROUBLESHOOTING.md ├── README.md └── README_zh.md # 本文件测试
python3 -m pip install -r requirements.txt pytest pytest tests测试覆盖了三个层面:改写 hook 的注入防护与 fail-closed 行为(test_cubesandbox_rewrite.py)、执行器的会话复用/状态持久化/并发串行化/超时处理(test_cubesandbox_exec.py),以及安装脚本的幂等性、配置白名单与卸载还原(test_hook_install.py)。其中值得关注的安全验证包括:状态文件拒绝符号链接、BASH_ENV等自执行变量被清洗、同会话并行调用严格串行(max_active_commands == 1)、安装脚本不向 hook 配置泄漏 provider API key。
排错
| 现象 | 可能原因 | 处理 |
|---|---|---|
| Bash 命令仍在宿主机执行 | hook 未注册 / Claude Code 未重启 | 重新执行hooks/install.sh并重启 Claude Code |
CUBE_TEMPLATE_ID is not set | .env缺模板 | 设置CUBE_TEMPLATE_ID(见cubemastercli tpl list),然后重新运行hooks/install.sh |
the cubesandbox SDK is required | 未装依赖 | pip install -r requirements.txt(需要cubesandbox与python-dotenv) |
Template not found | 模板 ID 错误 | 检查cubemastercli tpl list |
| 挂载被拒(警告) | 路径不在allowed_host_mount_prefixes | 在 CubeMasterextra_conf加前缀,或接受无挂载回退 |
更多详细踩坑记录(包括模板快照与 CPU 特性绑定导致的CpuidCheckCompatibility报错及其重建方法、npm/pip 安装类命令超时的参数调优等)见 TROUBLESHOOTING.md。
安全设计要点
- fail-closed:hook 无法安全改写时以非零退出阻断命令,绝不放行到宿主机;
- 防注入:原命令作为单个
shlex引用参数传入执行器,shell 元字符和换行无法越出; - 无条件改写:每条 Bash 调用都会被改写;已包裹的执行器调用若再次经过 hook,只会在沙箱内失败(沙箱内不存在宿主 hook 路径),绝不会落到宿主机;
- 自动批准:hook 对改写后的 Bash 调用返回
permissionDecision: "allow",Claude Code 的逐命令确认提示被抑制;请相应使用--permission-mode/ hooks 策略; - 凭据不外泄:安装脚本只复制白名单
CUBE_*值,不会把 provider API key 写进 hook 配置; - 状态文件防护:会话状态目录与文件分别以 0700/0600 权限创建,读写均拒绝符号链接,持久化环境会清洗
BASH_ENV/ENV/LD_PRELOAD/PROMPT_COMMAND等自执行变量(见 cubesandbox_exec.py 与其测试)。
这套方案的核心价值在于:它不要求模型学习任何新工具或新 prompt,而是在 Claude Code 的工具调用边界上做了一层透明的强制隔离——Bash 永远进沙箱,文件操作留在宿主机,二者互不越界。
【免费下载链接】CubeSandboxInstant, Concurrent, Secure & Lightweight Sandbox for AI Agents.项目地址: https://gitcode.com/GitHub_Trending/cu/CubeSandbox
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考