DeepTutor v1.4.1 技术解读:TutorBot 工具沙箱加固、按用户资源隔离与多模态图片回退
**DeepTutor v1.4.1(2026.05.27 发布)**是紧随 v1.4.0 GA 推出的"安全 + 稳定性"补丁版本,定位为 drop-in 升级:关闭 TutorBot 的默认 shell 执行入口、落实按用户数据隔离、修复 v1.4.0 引入的聊天输入回归,并新增面向特定 TutorBot 的 HTTP/SSE 对话 API 与多模态图片降级策略。读完本篇,你可以完整掌握该版本的安全加固面、需要感知的破坏性变更(尤其是 allow_shell_exec 语义)、新增 API 的用法,以及 ZIP 安全解压与网络设置背后的工程细节,并能结合仓库源码验证每个结论。
版本定位与升级路径
v1.4.1 的全部改动建立在 v1.4.0(2026.05.22 GA)基础上,属于同一条 v1.4 线的补丁发布。官方给出的升级方式是 drop-in(无缝替换):
# pip 安装用户直接升级
pip install -U deeptutor
# Docker 用户拉取最新镜像
docker pull ghcr.io/hkuds/deeptutor:latest
需要特别注意的是,本次发布包含默认行为变更:TutorBot 的 shell exec 能力从"默认开启"改为"默认禁用",升级后必须显式配置才能恢复(详见下文),因此即使是补丁版本,也建议在升级前核对现有 TutorBot 对 shell 工具的依赖情况。
从仓库的发布记录目录 assets/releases/past_releases 可以看到,v1.4.1 之后该线还持续迭代到 v1.4.15,v1.5.0 之后相关机制又做了进一步演进(例如 allow_shell_exec 在自动启动时被静默复位为 false 的问题 #531、无法持久化的问题 #534,均在 v1.4.x 后期修复)。下文会同时给出当前仓库中仍可核验的源码证据。
TutorBot 工具沙箱:shell exec 改为显式 Opt-In
这是 v1.4.1 最核心的安全变更,直接回应了多个高危漏洞(#518、#506)。变更要点如下:
- exec 工具不再默认注册。除非管理员为对应 bot 显式设置
allow_shell_exec=true,否则 shell 执行工具不会出现在工具清单中,LLM 无法调用它。 - 文件系统与 shell 访问默认收敛到 bot 工作区(workspace)内,工具不能越出该目录读写。
- 命令 deny-list 在"命令边界"处重新锚定(re-anchored at command boundaries),避免此前"子串匹配"带来的绕过窗口——例如把危险命令嵌入到参数、别名或拼接串中。
allow_shell_exec不能经由 update payload 翻转:即使某个 API 请求携带修改 bot 的 payload,也无法远程把该开关打开,杜绝了"先写入恶意配置再通过更新接口开启执行"的攻击链。
源码侧佐证:Deny 列表与执行链路
当前仓库中仍保留了同类防护的演进形态。聊天侧 shell 工具 deeptutor/tools/exec_tool.py 的模块注释明确写道:它是 TutorBot ExecTool 的 chat 侧对应物,每条命令都经由 deeptutor.services.sandbox 沙箱层执行(而非裸 subprocess),并把 deny 模式作为 OS 隔离之上的纵深防御(defence-in-depth)复用。
该文件顶部保留了与 TutorBot ExecTool 一致的 _DENY_PATTERNS 拒绝模式表,可以作为"命令边界锚定"的实际内容参考:
| 拒绝模式(正则) | 拦截目标 |
|---|---|
\brm\s+-[rf]{1,2}\b |
递归强制删除 |
\bdel\s+/[fq]\b / \brmdir\s+/s\b |
Windows 递归删除 |
| `(?:^ | [;&|]\s*)format\b` |
\b(mkfs|diskpart)\b |
磁盘管理 |
\bdd\s+if= |
块设备覆写 |
>\s*/dev/sd |
直接写裸设备 |
\b(shutdown|reboot|poweroff)\b |
系统关机重启 |
:\(\)\s*\{.*\};\s*: |
fork 炸弹 |
| `(?:^ | [;&|]\s*)(useradd|usermod|passwd|chpasswd|crontab)\b` |
注意正则中大量使用 \b(词边界)以及 ^|[;&|] 作为命令起始锚点,这正是"在命令边界锚定"的实现方式——format 只在与命令边界相邻时才被拦截,避免误伤 format 字符串 之类的正常用法。命令默认超时 30 秒、上限 300 秒(_DEFAULT_TIMEOUT = 30、_MAX_TIMEOUT = 300,见 exec_tool.py)。
执行时工具只负责构造 ExecRequest(含 command、workdir、mounts、ResourceLimits),真正的隔离由沙箱服务完成:当前轮次的工作区被 read-write 挂载为工作目录,_sandbox_user_id、_sandbox_workdir、_sandbox_mounts 等参数由服务端管线注入,LLM 永远无法自行提供,从而确保隔离边界掌握在平台侧而非模型侧。
升级动作
如果升级前确实依赖 TutorBot 的 shell 执行能力,需要在 bot 配置上显式开启:
allow_shell_exec = true
即便开启,工具访问范围仍被限制在 workspace 之内(沙箱挂载边界 + deny-list 双保险),不建议为了省事关闭这一层。
按用户资源隔离:目录、数据库与会话全链路收敛
v1.4.1 把多用户边界从"登录鉴权"下沉到"存储与运行时资源"层面,明确要求跨用户请求无法触达彼此数据。官方宣布隔离的清单包括:
- Book 根目录(教材/书目数据根);
- 会话数据库(session databases);
- turn-runtime 存储(每轮对话的运行时状态存储);
- TutorBot 目录;
并且 web/API 会话按 session 粒度 key 化——同一用户的不同会话之间、不同用户之间均不可互相读写。
当前仓库中的演进形态
从当前仓库源码看,这一隔离设计在后续版本中被持续固化并迁移:deeptutor/multi_user/paths.py 定义了清晰的目录拓扑:
data/user # admin 工作区根(管理员的根即 data/)
data/users/<uid> # 每个非 admin 用户独立工作区
data/partners/<id> # partner(合成用户)工作区
data/system # 系统级数据
其中 user-secrets 等按 owner 隔离的机密目录永不挂载给运行容器。文件中 migrate_legacy_multi_user_tree() 说明早期(v1.4.x 时代)的 "sibling multi-user/ 树"(即 PROJECT_ROOT/multi-user 下的 _system 与各 uid 目录)会在升级时一次性迁移到 data/ 布局——这与 v1.4.1 发布说明中"按用户隔离"落地于 deeptutor/multi_user/ 体系的历史实现相互印证。
配套的隔离面还包括权限模型层:deeptutor/multi_user 下的 grants.py(授权)、context.py(当前用户上下文)、identity.py(身份)、paths.py(路径解析)协同工作;对应测试集中在 tests/multi_user,例如 test_resource_isolation.py(资源隔离)、test_owner_bound_models.py(owner 绑定的模型)。v1.4.1 修复的多个 authz bypass(#516 跨 bot 文件管理、#515 跨会话 turn-regeneration、#514 book-confirmation)正是这类边界上的历史漏洞。
面向指定 TutorBot 的 HTTP / SSE 对话 API
应社区功能请求 #511,v1.4.1 为"与某一个特定 TutorBot 进行多轮对话"新增了两组外部客户端可用的端点:
POST /{bot_id}/chat—— 发起一轮对话,返回回合结果;POST /{bot_id}/chat/execute-stream(SSE)—— 流式执行,逐事件推送推理/执行进度与增量内容。
两者共同具备两个关键行为:
- auto-start:bot 尚未启动时,请求会先拉起该 bot 再处理对话,调用方无需关心生命周期;
- 持久 per-session 上下文:对话状态按会话持续保存,支持真正的多轮上下文,而非"一问一答"的无状态接口。
这套接口把 TutorBot 从"仅平台内可交互"扩展为"可被外部系统集成",例如通过脚本、定时任务或其他应用持续调用同一个 bot。
当前仓库中的对应实现痕迹
在当前仓库中,"bot" 抽象已向 partner / 通用 capability 方向演化,但实现骨架仍可对照:
- deeptutor/api/routers/plugins_api.py 提供 SSE 流式执行端点
/tools/{tool_name}/execute-stream与/capabilities/{capability_name}/execute-stream(内部由_execute_stream()生成 SSE 事件流); - 同一文件里
CapabilityExecuteRequest仍保留bot_id字段并注明是 legacy TutorBot 字段名,现用于寻址 partner——从源码注释可以确认 v1.4.1 时期{bot_id}寻址的正是当时所称的 TutorBot; deeptutor/partners/channels/base.py等渠道层沿用了 TutorBot 时代的执行管道,deeptutor/agents/chat/agentic_pipeline.py中仍可见 tutor 相关命名的 pipeline。
若你仍运行 v1.4.1,直接按文档使用 POST /{bot_id}/chat 与 /chat/execute-stream 即可;若已升级到更高版本,请以对应版本的 partner 路由与命名迁移说明为准(可对照 ver1-5-0.md 之后的发布说明)。
多模态图片回退:不再"静默丢图"
v1.4.1 修复了一类隐蔽的模态兼容性问题:Doubao / VolcEngine 等具备多模态能力的模型,因为没有在平台的 known-vision allowlist 中登记 capability 条目,此前携带图片的请求会被静默丢弃图片。新策略把"图片是否被接受"从"平台预判"改为"运行时验证 + 自动降级":
- 乐观注入(Stage 1):图片无条件附加到请求中发给每一个 provider/model——即使平台没有该模型的视觉能力条目,也不做前置拦截;
- 失败降级(Stage 2):如果携带图片的请求确实失败、且报错属于"模型不支持图片输入"、同时该模型不在已知视觉 allowlist 中,则把消息中的图片部分剥除,以纯文本重试该轮,而不是让整轮硬失败或静默返回错误结果。
关键设计细节是:已知支持视觉的 allowlist 模型不会走这条降级路径——因为它们理应支持图片,若请求失败就应当暴露真实错误,而不是"看似成功却返回了缺失图片信息后的纯文本答案",从而避免误导。
源码侧佐证:两级回退真实存在
当前仓库中该机制被完整保留并文档化。图片注入入口 deeptutor/services/llm/multimodal.py 的 prepare_multimodal_messages() 注释写明:
Images are injected optimistically for every provider/model — this function does not consult
supports_vision. … the Stage-2 fallback strips the images and retries as text-only.
其中还提及"正是最初的 Doubao/VolcEngine bug"催生了该设计。消息会被转换为 content-parts 数组(原始文本 + 图片),仅当附件为 url-only、而 provider 既不接受 URL 形式又无法解析为本地字节时才在该阶段丢弃(计入 url_images_dropped)。
降级执行点位于 deeptutor/core/agentic/labeled_step.py 的 run_labeled_step 重试缝隙中:检测到 is_image_input_unsupported(exc) 且 should_degrade_to_text(binding, model, messages) 成立时,调用 strip_image_parts_inplace(messages) 原地剥除图片(避免后续循环迭代再次携带),并通过 stream.progress 发出 "Model does not support image input; retrying without images." 的警告事件,最终重试为纯文本请求。should_degrade_to_text 的判断依据 supports_vision 是否在 allowlist 中登记,这条语义在 deeptutor/services/llm/provider_core/base.py 的 Stage-2 注释里同样得到确认。
安全 ZIP 上传与网络设置页
ZIP 知识库上传:逐成员校验,拒绝 Zip Slip 与 Zip Bomb
v1.4.1 为 .zip 知识库上传引入了完整的安全解压管线,核心原则是:压缩包逐成员(member-by-member)流过文档校验器,压缩包本身永不进入索引——只有通过校验的成员文档才会被纳入知识库。
校验边界在仓库中有精确实现,见 deeptutor/utils/archive_extractor.py 的 safe_extract_zip():
| 防护项 | 实现要点 |
|---|---|
| Zip Slip(路径穿越) | 成员名统一折叠为清洗后的 basename,破坏 ../ 与绝对路径攻击;写入前用 _is_within() 再次校验目标在根目录内 |
| Zip Bomb(条目爆炸) | max_entries 限制压缩包内非目录成员总数,超限即拒绝 |
| 压缩比异常 | 单成员 file_size / compress_size 超过 max_compression_ratio(默认 200.0)即判定可疑并跳过 |
| 解压超量 | 单成员按声明大小设置硬字节预算,流式写出时一旦超过即中止并报 "decompressed past its declared size" |
| 系统文件 | 跳过 __MACOSX/ 前缀与点开头(dotfile)成员 |
| 同名冲突 | 扁平化后重名的成员被跳过(记录原因),避免覆盖 |
模块注释明确点出了设计动机:常规解压实现既对 Zip Slip 无防护(可通过 ../ 或绝对路径写出任意文件),也"乐意写出任意文件类型";而该实现逐成员流式解压并施加上述全部约束。相关测试见 tests/utils/test_archive_extractor.py,与知识库路由 deeptutor/api/routers/knowledge.py 中的 ZIP 上传链路(配合 deeptutor/utils/document_validator.py)配套工作。
/settings/network:端口、API 基址与 CORS 归一化
新网络设置页把这些平台侧参数集中呈现、集中修改:
- 监听端口与公开 API 基址(public API base);
- CORS origins:新增归一化逻辑,容忍
host:port写法与结尾斜杠差异,避免"配置看起来一样但实际不匹配"的经典问题; - cookie 安全属性:
cookie_secure、cookie_samesite(当cookie_secure=true时同站点策略为none,并汇总出cross_site_cookie_ready状态); - "fetch models" 动作:从 OpenAI 兼容端点拉取模型 ID 列表,供配置选型。
当前仓库中该页面的后端即 deeptutor/api/routers/settings.py 的 GET/PUT /network(_network_settings_payload() 内部用 normalize_origins() 处理 origin 集合,并区分 explicit / permissive 两种 CORS 模式),而 "fetch models" 对应 POST /fetch-models——它是 deeptutor/services/llm/factory.fetch_models 的薄 HTTP 封装,按 binding + base_url + api_key 请求后返回 model_ids。这与发布说明中"#523(model fetching + notebook lookup)本地重实现后合入"的描述相符。
社区修复与变更全量盘点
v1.4.1 合入的社区贡献与内部修复可从发布说明中完整对齐:
安全闭包(Security)
| 编号 | 漏洞类型 |
|---|---|
| #518 | 经 shell 工具触发的 TutorBot RCE |
| #517 | 文件系统工具路径穿越(path traversal) |
| #516 | 跨 bot 文件管理鉴权绕过 |
| #515 | 跨会话 turn 重新生成鉴权绕过 |
| #514 | book 确认流程鉴权绕过 |
| #506 | ExecTool 经聊天执行 LLM 提供的 shell 命令(首个加固在 PR #507) |
其中 #506→#507 与 #518 的修复即上文"沙箱 opt-in"的直接动因;#516/#515/#514 则是"按用户资源隔离"一节所覆盖的鉴权边界。
缺陷修复(Bug fixes)
- #520:v1.4.0 回归——首轮对话后聊天输入被禁用;
- #521 / PR #509:长文档导致知识库 embedding 失败(chunking 管线修复);
- #512 / PR #513:Docker 环境下新用户无法创建个人资料(同时补上空状态页的 profile 按钮);
- #527 / PR #528:Qwen 系推理模型原生工具调用失败;
- PR #508:GPT-5 init-wizard 的 token 参数修正。
合并 / 重构的 PR
- #528 推理模型原生工具调用、#524 超大会话事件截断、#513 空状态 profile 按钮、#509 chunking 管线修复、#508 GPT-5 探测、#507 ExecTool 加固;
- 社区贡献 #522(zip 上传) 与 #523(模型拉取 + notebook 查询) 未直接合入原始提交,而是在本地重实现后随本版本发布。
升级注意事项与运维建议
- Drop-in 升级:
pip install -U deeptutor;Docker 用户拉取ghcr.io/hkuds/deeptutor:latest。从 v1.4.0 升级无需手工迁移。 - TutorBot shell exec 已默认禁用:这是本版最需要感知的变更。仍在依赖 shell 能力的 bot 须显式设置
allow_shell_exec=true;即便开启,工具访问仍被限制在工作区内,且该开关不可经由 update payload 远程打开,请在服务端配置面操作。 - 跨站 HTTPS 鉴权:若部署于跨站(cross-site)HTTPS 环境,需要显式设置 CORS origins,并开启
cookie_secure=true(同站点策略随之变为none),否则鉴权 cookie 在跨站请求中不会被携带。可直接在新增的/settings/network页面完成上述配置并核对cross_site_cookie_ready状态。
小结
v1.4.1 是"小版本、大安全"的典型:它以一次默认行为变更(shell exec opt-in)同时关掉了 RCE 与配置翻转两条路径,用 per-user / per-session 隔离补齐了此前逐点修复的鉴权绕过,用"乐观注入 + 失败降级"而非"前置能力预判"的思路根治了多模态丢图,并为外部集成打开了一条带持久上下文的 HTTP/SSE 通道。结合 v1.4.0 发布说明与后续 v1.5.0 等版本的记录,可以清晰看到这套安全基线(沙箱服务化、data/users/<uid> 目录隔离、normalize_origins CORS 语义)如何被后续版本持续继承与扩展——这也是阅读一份补丁版本时最有价值的脉络。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust4.21 K635- DDeepSeek-V4.1-FlashDeepSeek-V4.1-Flash 是一个多模态混合专家(MoE)模型,拥有 5520 亿骨干参数,并支持最多一百万 token 的上下文长度。该模型原生支持图像和文本输入,并以自回归方式生成文本Python60
jforgamejforgame是一个一站式游戏服务器开发框架。包含游戏服务器开发所需要的各种组件,比如网关,socket服务端与客户端,自定义高效消息编解码,游戏热更新,游戏通用工具等等。包含游戏服,跨服,匹配服,后台管理系统等实现,同时提供大量业务案例以供学习。亦可用于其他socket应用,例如及时聊天等。Java131
fizz-gateway-nodeAn Aggregation API Gateway in Java . FizzGate 是一个基于 Java开发的微服务聚合网关,是拥有自主知识产权的应用网关国产化替代方案,能够实现热服务编排聚合、自动授权选择、线上服务脚本编码、在线测试、高性能路由、API审核管理、回调管理等目的,拥有强大的自定义插件系统可以自行扩展,并且提供友好的图形化配置界面,能够快速帮助企业进行API服务治理、减少中间层胶水代码以及降低编码投入、提高 API 服务的稳定性和安全性。Java80
certd开源SSL证书管理工具;全自动证书申请、更新、续期;通配符证书,泛域名证书申请;证书自动化部署到阿里云、腾讯云、主机、群晖、宝塔;https证书,pfx证书,der证书,TLS证书,nginx证书自动续签自动部署JavaScript90
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python290