docs/review/box-issues.md
更新日期: 2026-06-02 分支:
feat/sandbox(LangBot + langbot-plugin-sdk) 相关文档: 架构分析 | Session 作用域 | Runtime 对比 | 测试覆盖 | toB 分析
自部署社区版已具备发布条件:默认 stdio 模式、box 为可选项;box 关闭 / 不可用时后端、前端、工具、skill、stdio-MCP 均能干净降级(清晰报错、不崩溃);配置向后兼容(旧 data/config.yaml 可直接启动);无新增 ORM 模型、无迁移欠债;市场安装失败不会破坏实例。CI 全绿。
本清单只保留发布 SaaS / 多租户 / 公网暴露前必须处理的阻塞项。社区版(可信、单运营者、内网)不受这些项阻塞——它们的风险面在"不可信调用方能直接触达 Box 控制面"或"多租户共享资源"的场景才成立。
| 项 | 处理 |
|---|---|
| 工具调用循环无上限 (原 #13) | localagent.py 增加 MAX_TOOL_CALL_ROUNDS=128,超限优雅终止(cafef1a3) |
| 配额校验同步遍历阻塞事件循环 (原 #10) | _enforce_workspace_quota 改 async,工作区遍历走 asyncio.to_thread(cafef1a3) |
host_path 挂载白名单 (原 #3 的 LangBot 侧) | pkg/box/service.py allowed_mount_roots 白名单,空列表时拒绝一切宿主挂载 |
重复的 _is_path_under (原 #12) | 已去重,仅保留一处定义 |
| 重连 / 心跳 / Windows 兼容 / nsjail image 字段 / 前端 Box 状态接入 | 见上一轮 review 记录,均已合入 |
box/server.py — Action RPC WS (/rpc/ws) 与 managed-process relay (/v1/sessions/{id}/managed-process/{pid}/ws)ws.prepare 后直接服务,无任何 token / 鉴权;box 默认绑定 0.0.0.0:5410。任何能触达该端口者可发起 EXEC、创建 session、attach 任意 session 的 managed-process stdin/stdout、甚至 SHUTDOWN。LangBot→box 的 INIT 也未下发任何凭证。docker-compose.yaml 的 langbot_box 未把 5410 发布到宿主(爆炸半径限于内网 bridge);但 box 挂载了 /var/run/docker.sock,同网络的任意服务(含被攻破的插件)→ 宿主 root。若运营者把 5410 发布到宿主或独立以 0.0.0.0 起 box,则完全裸奔。pkg/box/policy.py(SandboxPolicy / ToolPolicy / ElevatedPolicy 全项目无引用);pkg/provider/tools/loaders/native.py;pkg/provider/tools/toolmgr.pyexec/read/write/edit/glob/grep)按"box 是否可用"全有或全无地暴露,无 per-pipeline 的 exec 网关 / 工具白名单 / 沙箱模式 / 权限提升控制。只要 box 可用,任何使用 local-agent + 函数调用模型的 pipeline 都能跑任意 shell。exec、可用工具白名单、沙箱网络/只读模式。box/runtime.py _get_or_create_session 的 _sessions dict 无容量限制——可变 session_id 的恶意调用可无限创建容器,耗尽宿主 CPU/内存/PID/磁盘。_get_or_create_session 时机会性清理,无独立周期任务;一波创建后转静默会永久泄漏容器。max_sessions 上限(拒绝或 LRU),加独立周期 reaper(如 60s)。pkg/box/service.py _enforce_workspace_quota(应用层 read-then-check);SDK 侧 workspace_quota_mb 仅记录/透传,无 --storage-opt size= 等内核/FS 限额dd/fallocate)可在检查间隙撑爆磁盘,事后检查只能补救。--storage-opt size= 做内核级限制,或 Redis 原子计数预留式配额。box/security.py _BLOCKED_HOST_PATHS_POSIX;box/backend.py 的 extra_mounts 处理/(前缀匹配,host_path="/" 可通过,挂载整个宿主 fs);用户 home、/usr、/opt、/tmp 也未拦截。② validate_sandbox_security 只校验 spec.host_path,从不遍历 spec.extra_mounts——LangBot 侧 allowed_mount_roots 也只校验 host_path。当前 extra_mounts 仅由 build_skill_extra_mounts 内部填充(agent 不可达),但缺乏纵深防御:一旦 S1 的无认证 RPC 被触达,extra_mounts 可挂任意宿主路径,两层都不拦。/(或改白名单);extra_mounts 在 SDK 与 LangBot 两侧都纳入挂载校验。box/backend.py 的 docker run 组装--cap-drop=ALL、--security-opt=no-new-privileges、非 root --user;叠加挂载 docker.sock,逃逸面偏大。box/runtime.py _get_or_create_session:self._lock 持有期间调用 backend.start_session()(docker run / nsjail 启动 / E2B Sandbox.create)box/server.py 直接读 runtime._sessions 私有字段、绕过锁,并发下可能读到不一致状态——应加公共访问方法。pkg/provider/tools/toolmgr.py execute_func_call 按优先级分发,plugin/MCP 若有同名 exec/read/write/... 工具会被静默遮蔽——应加命名空间或冲突告警。box/runtime.py INIT/handshake 与 backend 实例化的残留竞态(仅"纯远程 WS box 先启动、LangBot 后连"场景成立;stdio/compose 路径下 config 经 env 在 spawn 时已就位,无竞态)——应在 INIT 完成前拒绝业务 action。extra_mounts 在容器创建时固定(SDK runtime.py 兼容性检查不含 extra_mounts);长生命周期共享 session 后续新激活的 skill 不会挂上(当前缓解:创建时挂上 pipeline 绑定的全部 skill)——动态绑定场景需销毁重建或文档说明。