DeerFlow Sandbox:隔离执行与虚拟路径
agent 要读 CSV、写脚本、再 python stats.py。若命令直接落在宿主机,脚本 bug 或恶意代码会伤到真实系统;多用户共享同一环境还会串文件。DeerFlow 给每个 thread 一块隔离工作区:Sandbox。agent 在里面折腾,宿主机与其他用户数据尽量不受影响。
抽象接口与三种 Provider
Sandbox(sandbox/sandbox.py)规定统一能力:execute_command、read_file、write_file、download_file、update_file、list_dir、glob、grep。实现可以是本地目录、Docker 容器或 K8s Pod,agent 只面对同一套方法,不必关心底层形态。
SandboxProvider 管生命周期:acquire(thread_id) 获取或创建并返回 id,get 按 id 查找,release 释放。acquire 带上 thread_id,同一对话复用同一沙箱,不同对话互不共用。配置里常见三类:LocalSandboxProvider(本地路径映射,开发调试,隔离偏弱)、AioSandboxProvider(Docker,隔离更强,偏生产)、K8sSandboxProvider(Pod,适合大规模)。config.yaml 改 sandbox.use 一行即可切换。
虚拟路径、映射与逃逸防护
统一视图大致是:/mnt/user-data/workspace(可写工作区)、uploads(上传)、outputs(交付产物),以及只读的 /mnt/skills,还有 /mnt/acp-workspace。Local 侧用 PathMapping 把容器路径映到 users/{user_id}/threads/{thread_id}/... 下的真实目录;映射项含 container_path、local_path、read_only。
_find_path_mapping 按 container_path 长度降序匹配,更长的前缀优先,避免 /mnt/user-data 抢在 /mnt/user-data/uploads 前面。解析后再 relative_to(local_root),逃逸则 PermissionError。映射对了还要再验一次是否仍在根内,多拦一层路径逃逸。
命令输出里的本机绝对路径会反向替换成虚拟路径,避免暴露用户名与目录结构,也避免 agent 只认 /mnt/... 却收到宿主机路径。读文件时,反向替换主要针对 agent 自己写过的路径集合;用户上传内容不擅自改写,尊重原始数据。
/mnt/skills 映射带 read_only=True。agent 试图写入会碰到只读错误(如 EROFS),防止改坏公共技能或用改技能的方式注入流程。所有 thread 看到的技能内容应一致。
延迟初始化、缓存与用户隔离
SandboxMiddleware 在工具调用前后看 sandbox id:工具内部可能通过 ensure_sandbox_initialized 首次 provider.acquire;若发现新建了沙箱,用 Command 写进 agent 状态,后续复用。对话若只闲聊、从不调沙箱工具,可以不建沙箱,省资源。Local 按 thread 缓存实例,默认上限约 256,超出 LRU 淘汰。Local 的 release 常为空操作,刻意保留缓存与 _agent_written_paths,跨 turn 还能看见上一轮文件。进程退出时 shutdown_sandbox_provider 统一清理。
张三 thread_a 与李四 thread_b 拿到不同 sandbox id(例如 local:thread_a),映射到不同物理目录。虚拟路径字符串可以相同,真实路径不同,互不可见。防护叠了虚拟路径约束、按 thread 映射、物理目录分离,以及操作系统权限。
审计、AIO 串行锁、download_file
SandboxAuditMiddleware 只拦截名为 bash 的调用。高危正则包括危险 rm -rf、管道进 shell、mkfs、重定向覆盖 /etc/ 等,判定 block 则直接返回错误态 ToolMessage,不执行。中危(如部分 pip install、chmod 777)执行但附加警告。其余放行。分类两遍:先扫整条命令抓跨语句攻击,再拆复合命令逐段分类。无论判定如何都会写审计日志(时间戳、thread、命令、verdict),便于事后排查。agent 收到 block 消息后可换更安全的做法,整轮不必崩溃。
AioSandbox 容器内维护单个持久 shell 会话。并发命令会损坏会话并返回 ErrorObservation(见 #1433)。实现用 threading.Lock 串行化 execute_command;若检测到损坏签名,换新会话 id 重试。正确性优先于并行吞吐,这是工程上刻意的取舍。
download_file 是把文件带出沙箱的路径之一。能确认的检查至少包括:把路径规范化后按段扫描,遇到 .. 直接 PermissionError,拒绝路径穿越。其余校验以仓库实现为准。
生命周期怎么记
对话开始时可以还没有沙箱。第一次真正用到沙箱工具才 acquire;同一 thread 后续调用复用;turn 之间 Local 侧常留在缓存里;长期不用被 LRU 踢掉;应用关闭时统一 shutdown。延迟创建省资源,跨 turn 复用则让上一轮写的脚本下一轮还能跑。
统计 CSV 那条路径可以走一遍:用户提需求 → 首次工具调用触发 SandboxMiddleware / ensure_sandbox_initialized → read_file 读 uploads → write_file 写 workspace 脚本 → execute_command 跑 Python → 结果回用户。路径始终是虚拟的 /mnt/user-data/...,底层可能是本机目录或容器挂载。
DeerFlow Sandbox:隔离执行与虚拟路径