Skip to content

沙箱架构与文件工具

本章目标:

  1. 理解沙箱抽象层(Sandbox / SandboxProvider)与两种实现(Local / Aio)的取舍
  2. 看懂虚拟路径映射:Agent 看到的 /mnt/user-data/* 如何翻译成宿主物理路径
  3. 弄清 allow_host_bash: true 的安全边界与 a-cdm 为何接受这个权衡

TL;DR

沙箱给 Agent 提供"可执行命令 + 读写文件"的隔离环境。抽象层是 Sandbox(execute_command/read_file/write_file/list_dir)+ SandboxProvider(acquire/get/release 生命周期)。a-cdm 用 LocalSandboxProvider(单例,直接在宿主机执行),Agent 看到的虚拟路径 /mnt/user-data/{workspace,uploads,outputs}/mnt/skills/mnt/kingdeepath_mappings 翻译成宿主 backend/.deer-flow/threads/{thread_id}/...。文件工具(ls/read_file/write_file/str_replace/grep/glob/bash)在 sandbox/tools.pyconfig.yaml 开了 allow_host_bash: true,意味着 LocalSandbox 不是安全隔离边界——a-cdm 因"内部可信、非公开"接受这个权衡。

Overview(为什么 Agent 需要沙箱,又为什么是"虚拟路径")

让 LLM 跑 bash、读写文件是 Agent 能"干活"(分析数据、生成报告、跑脚本)的前提。但直接给 LLM 宿主机的真实路径有两个问题:① 不同 thread 的文件会互相污染;② LLM 可能拼出 /etc/passwd 这种宿主敏感路径。

deer-flow 的解法是虚拟路径沙箱:Agent 永远只看到 /mnt/user-data/workspace 这类稳定的虚拟路径,沙箱层在执行前把虚拟路径翻译成 backend/.deer-flow/threads/{thread_id}/user-data/workspace 这种按 thread 隔离的物理路径。这样 LLM 的"心智模型"是干净的容器视角,实际落盘按线程隔离,prompt 里也不暴露宿主真实结构。SandboxProvider 抽象则让"本地直接执行"和"Docker 真隔离"可以换实现而 Agent 代码不变。

Architecture:抽象层 + 两种实现

抽象定义关键方法Source
Sandbox(ABC)执行环境接口execute_command read_file write_file list_dirdeer-flow/backend/packages/harness/deerflow/sandbox/sandbox.py:6-85
SandboxProvider生命周期管理acquire get releasedeer-flow/backend/packages/harness/deerflow/sandbox/sandbox_provider.py

两种实现(config.yamlsandbox.use 选):

实现隔离a-cdm 用Source
LocalSandboxProvider无(直接宿主机执行,单例)✓ 当前sandbox/local/local_sandbox_provider.py:13
AioSandboxProviderdocker-in-docker 真隔离长期目标(未启用)community/aio_sandbox(config.yaml:698)

LocalSandboxProvider 是单例(local_sandbox_provider.py:10:103-106):acquire() 时若 _singleton is None 就建 LocalSandbox("local", path_mappings=...),所有 thread 共用同一个 LocalSandbox 实例(sandbox_id 恒为 "local"),靠虚拟路径里的 thread_id 做隔离而非靠多实例。release() 是 no-op(单例无需清理,local_sandbox_provider.py:116)。_RESERVED_CONTAINER_PREFIXES(:51)保留 /mnt/skills/mnt/acp-workspace/mnt/user-data 三个前缀不被用户 mount 覆盖。

Components:虚拟路径系统

Agent 视角虚拟路径宿主物理路径用途Source
/mnt/user-data/workspacebackend/.deer-flow/threads/{tid}/user-data/workspaceAgent 工作区deer-flow/backend/CLAUDE.md(Virtual Path)
/mnt/user-data/uploads同上 /uploads用户上传文件同上
/mnt/user-data/outputs同上 /outputs产出文件(present_files 才可见)同上
/mnt/skillsdeer-flow/skills/技能目录config.yaml:802-811
/mnt/kingdee/data/erp-wiki/kingdee(bind mount)ERP wiki(a-cdm 定制 mount)config.yaml:680-683
/mnt/acp-workspace{base}/threads/{tid}/acp-workspace(只读)ACP agent 工作区deer-flow/backend/CLAUDE.md

翻译靠 replace_virtual_path() / replace_virtual_paths_in_command(),is_local_sandbox()sandbox_id == "local"(deer-flow/backend/CLAUDE.md Virtual Path System)。/mnt/kingdee 是 a-cdm 定制——config.yaml:680-683sandbox.mounts 把它映射到宿主 /data/erp-wiki/kingdee,而 langgraph 容器又做了同名 bind mount(见 Aegra 章),确保 sandbox bash 在容器内 subprocess 跑时该路径可达。

文件工具速查表

config.yaml:604-636 把这些工具按 group 注册,use 指向 sandbox/tools.py:

工具group作用截断Source
lsfile:read目录树(≤2 层)ls_output_max_chars(20000)config.yaml:605-607
read_filefile:read读文件(可指定行范围)read_file_output_max_chars(50000),head 截断config.yaml:609-611
globfile:read文件名匹配(max 200)config.yaml:613-616
grepfile:read内容搜索(max 100)config.yaml:618-621
write_filefile:write写/追加,自动建目录config.yaml:623-625
str_replacefile:write子串替换(单/全部)同路径串行化按 (sandbox.id, path) scopeconfig.yaml:627-629
bashbash执行命令(路径翻译)bash_output_max_chars(20000),中间截断config.yaml:631-636

bash 工具仅在隔离 shell 或显式 allow_host_bash: true 时激活(config.yaml:632-633 注释)。截断策略有讲究:bash 用中间截断(头+尾,因错误可能出现在任何位置),read_file/ls 用头截断(内容前置)。

Data Flow:一次 bash 调用

Configuration / 安全标注

配置安全含义Source
sandbox.useLocalSandboxProvider⚠️ 无隔离,直接宿主机执行deer-flow/config.yaml:667
sandbox.allow_host_bashtrue⚠️⚠️ 放开宿主 shell,LocalSandbox 不是安全边界deer-flow/config.yaml:671-673
sandbox.mountskingdee read_only: false⚠️ Agent 可写 ERP wiki 目录deer-flow/config.yaml:680-683
bash_output_max_chars20000中间截断deer-flow/config.yaml:689

allow_host_bash: true 是本项目最高风险的单个配置。config.yaml:671-673 注释写明权衡:LocalSandboxProvider 不是安全隔离边界,a-cdm 因"内部可信团队、非公开暴露"启用以支持自研 skill(如 attendance-analysis),长期应迁移到 AioSandboxProvider(docker-in-docker,火山 all-in-one-sandbox 镜像)。这条与红线安全段直接相关,也是为什么前面"鉴权授权""公网边缘加固"那么严——因为一旦 Agent 被滥用,bash 是直通宿主的。

Common Pitfalls / 实战 Tips

  • LocalSandbox 是单例,sandbox_id 恒为 "local":多 thread 不是多实例,隔离全靠虚拟路径里的 thread_id。别假设"每个 thread 一个 sandbox 进程"。
  • /mnt/kingdee 路径在容器内必须可达:依赖 langgraph 容器的同名 bind mount(Aegra 章),漏配会让 sandbox bash 找不到 ERP wiki。
  • allow_host_bash: true 不要随手关也不要扩大:关了自研 skill 坏;真要收紧应迁 AioSandbox 而非小修。改它前读 config.yaml:671-673 的权衡说明。
  • outputs 要 present_files 才对用户可见:写到 /mnt/user-data/outputs 不等于用户能看到,需 present_files 工具显式呈现(见工具装配章)。
  • str_replace 串行化按 (sandbox.id, path):同路径并发替换会串行,隔离沙箱不会因相同虚拟路径互相阻塞。

References

  • deer-flow/backend/packages/harness/deerflow/sandbox/sandbox.py:1-85 — Sandbox 抽象接口
  • deer-flow/backend/packages/harness/deerflow/sandbox/local/local_sandbox_provider.py:1-116 — 单例 Provider(本章主源)
  • deer-flow/backend/packages/harness/deerflow/sandbox/local/local_sandbox.py — LocalSandbox 实现
  • deer-flow/backend/packages/harness/deerflow/sandbox/tools.py — ls/read/write/str_replace/bash 工具
  • deer-flow/config.yaml:666-691 — sandbox 配置 + allow_host_bash + mounts(本章主源)
  • deer-flow/backend/packages/harness/deerflow/sandbox/middleware.py — SandboxMiddleware 生命周期
PageRelationship
deer-flow 引擎配置体系本章 allow_host_bash/mounts 定制在该章首次提出
Agent 中间件链机制本章沙箱由该章 SandboxMiddleware/SandboxAudit 管理
Aegra 运行时与 LangGraph本章 /mnt/kingdee 依赖该章容器 bind mount
SKILL 技能系统本章 /mnt/skills 是该章技能的沙箱视图
MCP 集成与工具装配本章文件工具由该章 get_available_tools 装配

公司内部参考 · 由 claude-wiki-gen 基于源码自动生成的二次分析