#OpenClaw #qBittorrent #最小权限 #网络隔离

记录 OC-23:让微信里的“帮我下载 X”能够进入自动化流程,同时不把网页文本、模型判断或一个 magnet 直接等同于下载权限。

相关笔记《OpenClaw 本地记忆落地与无审批能力边界》 ·
《OpenClaw Exec 审批与生命周期治理》 ·
《OpenClaw 后续功能规划》 ·
《N100 安全事件响应与 fail2ban 加固》


一、问题不在 qB API,而在权限兑换

qBittorrent 的新增、暂停、恢复和删除接口并不复杂。真正危险的是这条隐含链路:

1
网页内容 → 模型相信它 → 直接调用下载接口

搜索结果和网页正文都可能包含 prompt injection,也可能指向内网、重定向到不同主机,或提供与页面描述不一致的 torrent。仅靠 system prompt 要求模型“注意安全”,不能形成可审计的权限边界。

因此这轮把流程拆成三个角色:

1
download-discovery → download coordinator → deterministic Gate → qB executor
  • discovery 只有 web_searchweb_fetch,没有 shell 或 qB 权限;

  • coordinator 接收微信意图和结构化结果,但不能直接浏览网页或执行 shell;

  • Gate 检查 URL、torrent、infohash 和文件清单,再签发短期单次票据;

  • executor 只接受有效票据,并且只连接 qB 管理网络。

网页负责提供线索,模型负责组织意图,确定性代码才负责兑换执行权限。

二、票据与来源验证

Gate 对 torrent 使用严格 bencode 解析并复算 infohash,同时检查:

  • URL 凭据、协议、每一跳重定向和 DNS 结果;

  • 私网、CGNAT、IPv6 ULA 等不可访问目标;

  • tracker 数量、文件数量、目录深度和名称长度;

  • 路径穿越、控制字符、Unicode 规范化异常;

  • ISO、可执行文件、压缩包等高风险扩展名。

票据绑定用户身份、微信会话、动作、内容摘要和短有效期。executor 维护一次性消费记录,因此篡改、过期、重放和跨会话使用都会被拒绝。签名材料只存在于受限 secret 中,不进入模型上下文、审计或本文。

对于高风险内容,系统不会因为来源看起来“官方”就自动恢复。例如 Ubuntu 官方 ISO
可以通过来源和元数据检查,但仍要求用户对具体文件作确认。

三、网络和文件隔离

Gate 与 executor 不共享相同网络能力:

组件 公网 qB 管理网 家庭 LAN
discovery / Gate SSRF 规则拒绝
executor
qBittorrent tracker/HTTP 本机 WebUI 保留 防火墙拒绝

新下载先进入 N100 的隔离目录。该目录以 nosuid,nodev,noexec 挂载,即使内容本身恶意,也不能直接借下载目录获得执行语义。qB 到家庭 LAN 的出站被受管规则阻断,公网 tracker、
DNS 和普通 HTTP 保持可用,WebUI 仍可从既有管理入口访问。

四、赛马、删除与急停

多个候选可以创建一个 race 组,但自动裁剪仅作用于本系统创建的组,并有 15 分钟保护窗口。普通“删除记录”和“删除文件”没有被混入免审批入口,仍保留确认。

独立 kill switch 只停止本系统类别的活动任务,不删除数据。审计写入失败时 executor
直接拒绝动作,而不是在没有证据的情况下继续。

五、验收结果

  • 12 项恶意 fixture 全部通过;

  • 篡改、过期、重放、跨会话和 metadata re-gate 被正确处理;

  • 新增、查询、暂停、恢复、赛马、裁剪和测试清理完成;

  • qB 访问家庭路由器被阻断,公网访问与 WebUI 正常;

  • 健康快照同时报告实际 ip_forward=1

  • forwarder 为受管用户服务,Linger=yes

  • 测试清理后,qB 的 17 个原有任务与变更前完全一致。

OpenClaw 仍为 2026.6.10,生产 bundle、全局超时及 qB/OpenClaw 账号密码均未修改。

六、仍然保留的边界

这套系统解决的是执行安全和权限最小化,不是全球版权数据库。它无法凭网页文本证明任意影视来源的授权状态;遇到无法形成机器可验证来源的内容时,应向用户索取明确来源或停在发现阶段。

微信里的通用交互审批仍是独立的 OC-22。D2b 只在精确版本源码上制作了 ACP 会话断开时终止等待审批的窄修复;隔离聚焦测试 13/13 通过。它没有修改生产 bundle,也没有用放宽 shell 或增大全局超时绕过生命周期缺陷。

七、微信保留 main,下载按任务分流

最初把微信账号整体绑定到 download coordinator 虽然简单,却会让日常对话、邮件和运维请求也落入下载 agent。最终改成:

1
2
3
4
微信 → main
├─ 普通对话与其他任务 → main 原会话
└─ 明确下载意图 → before_dispatch → 持久 download 会话
└─ 每次发现使用一次性 discovery 会话

分流在模型运行前由启动时加载的插件完成,只识别新增、暂停、恢复、进度和赛马等窄意图。下载会话由可信的微信账号与发送者身份派生,HTTP 请求不能通过自带字段冒充微信用户。
main 里的旧 qB skill 已退出自动发现,避免模型再次尝试读取 qB 凭据或通过 shell 直连 API。

发现层也不再把通用 sessions_spawn 暴露给 coordinator,而由单用途
download_discover 内嵌启动隔离 agent。模型输出即使带 Markdown fence、冗余字段或同时给出 torrent 与 magnet,也会先经过固定 schema、长度、候选数和来源字段归一化;网页原文不会直接进入有 qB 权限的上下文。

这次真实冒烟还暴露了 llama.cpp 的 grammar 上限:工具 schema 中 8K/16K 字符重复范围会被当前版本以 “number of repetitions exceeds sane defaults” 拒绝。修复方式是让模型侧 schema
只描述字符串,实际长度与内容边界继续由 Gate/Executor 强制执行,没有放宽执行策略。

最终验证包括:

  • main 到持久 download 会话的查询、暂停、恢复均无需 exec;

  • Ubuntu 官方 torrent 可完成 discovery → Gate,.iso 因高风险扩展名正确停在逐次确认;

  • 合成 .mkv 的添加、查询、暂停、赛马、篡改、重放与跨会话隔离通过;

  • 插件在网关启动清单中可见,测试会话与任务清理后 qB 回到原有 17 个任务;

  • exec 审批文件未变,日志不再出现读取 qB 密码的命令。

真实微信冒烟随后补出了一个渠道上下文差异:before_dispatch 能识别下载意图,但该版本没有在钩子上下文中完整提供 sender。首版因此 fail-closed,框架回退 main 并生成了一次未执行的 qB curl 审批。修复后,钩子只允许从当前 main session 中 provider 明确为
openclaw-weixin 的持久 origin 补齐身份,与 HTTP handoff 使用相同信任边界;请求正文仍不能提交或覆盖账号、发送者和会话身份。

另一个兼容性问题来自历史任务:原 /query 只看 OC-23 ownership DB,因此改造前或 WebUI
手动添加的 qB 任务会被误报为“没有下载”。最终仅给当前所有者微信身份增加了哈希匹配的全局只读 inventory;其他身份仍只能读取各自会话任务,暂停、恢复和删除也没有随可见范围一起扩大。真实复测可读取全部 17 个既有任务,非所有者合成查询仍为空。

仍有一项后续能力:OC-24 将补齐“最高综合画质”候选排序,以及下载完成后的实际视频规格、默认音轨和中文字幕验收。它不会改变本篇建立的 Gate、会话隔离和普通删除确认边界。

八、从正则分流升级为本地语义分类

确定性正则适合识别“下载进度”“暂停全部下载”等明确控制语句,但面对“刚发你的链接开跑”
“前一个源死了,找个活的替换”等省略表达会持续漏判。继续堆关键词只能扩大维护成本,还会把“如何下载文件”“不要下载,只比较版本”等讨论性请求误送进下载 agent。

这轮用两批完全合成的日常表达比较了多种本地方案:

候选 准确率 下载召回 其他召回 结论
0.6B / 1.5B 生成式小模型 46.7%–76.7% 6.7%–80.0% 73.3%–93.3% 否决
多语言 NLI / MiniLM 56.7%–76.7% 66.7%–86.7% 46.7%–66.7% 否决
本地 Qwen3.6 35B 96.7% 93.3% 100% 上线

35B 的热态短提示并不携带 OpenClaw 的完整系统上下文,未见 holdout 的 P50/P95 仅为
328/413 ms。容器内真实调用多数为 0.34–0.51 秒;一次与其他推理排队时达到 2.08 秒,因此生产超时设为 3 秒。

分类器只接收当前微信消息,使用既有局域网 llama-server,不调用工具、不浏览网页、不读取
secret,只能返回固定的 download|other JSON。只有 download 且置信度至少 0.8 才会分流;低置信、无效 JSON、超时或桌面模型停机都留在 main。提示注入、明确否定、翻译和知识问答均在测试中保持为 other

“查看下载进度”仍保留确定性快速通道,避免显卡被桌面应用占用时失去最常用的只读能力。这次升级只改变会话分流,不改变 Gate、票据、qB executor、普通删除确认、网络隔离或审批策略。

九、EVA 日常短句端到端收口

最终以真实日常表达“帮我下载 EVA 旧版 TV 版”重新验收。第一轮暴露的并不是权限缺口,而是发现层的可用性和生命周期问题:搜索 provider 遇到 challenge、网页抓取超时、模型反复创建新的 discovery 会话,以及详情页被误当作 .torrent 解析。

对应修复保持在窄边界内:

  • 搜索切换到既有受控 provider,每个隔离 discovery 最多两次搜索、两次抓取;

  • download 外层每个运行只允许一次 discovery,预算耗尽后必须使用已有结果或明确失败;

  • 固定 JSON 解析兼容 Markdown fence 和控制字符,但候选数量、字段和长度仍由代码检查;

  • 只有精确匹配 Nyaa host 与 /view/<数字> 的详情页才转换为站点的/download/<数字>.torrent,随后仍走 SSRF、bencode、infohash 和文件策略检查;

  • handoff 错误被插件声明为已处理,main 同时硬拒绝 qB 相关 exec 与通用子代理,因而锁冲突或模型失败也不会退回读取凭据、shell 或直连 qB。

最终请求在约一分钟内完成发现、候选检查、Gate 签票和 qB 新增,选中 1080p BluRay、
HEVC 10-bit 的 26 集版本。随后“查看下载进度”和“暂停”均沿同一个微信派生 download
会话执行,qB 实际状态为 stoppedDL、速度为 0,待审批队列为空。

这次还补了 qBittorrent 5.2 的兼容细节:新增任务需要 stopped=true,旧
paused=true 已不足以保证真实暂停。executor 现在同时发送新旧参数以兼容版本差异;没有升级 qB、OpenClaw,也没有修改账号密码、生产 bundle、全局超时、普通删除审批或
allowlist。

上述实现阶段当时完成,但后续真实日志又出现 discovery context overflow、llama.cpp
grammar 初始化失败、download session write lock 超时、可信 handoff 失败,以及删除确认状态回到 main 的回归。因此 OC-23 已重新打开,不能把“组件测试通过”等同于生产全生命周期稳定。

下一次只在 desktop llama-server 可用的窗口,用微信真实短句重新覆盖:
main 分流、隔离发现、多候选质量报告和赛马、Gate 签票、新增、进度、暂停、恢复、无源项清理,以及绑定确认删除。全程不得回退到通用 exec、shell、凭据读取或无限重试;出现错误必须给出明确原因且不留下孤儿任务。GPU 被游戏占用、11435 由用户手动关闭期间暂停这项模型型回归,不把计划停机误报为下载链路故障。

下载完成后的真实名称/规格刷新、最高综合画质排序、默认音轨和中文字幕检查仍归入
OC-24,而不是扩大当前下载执行权限。