SlimeWorks

节点服务(Node Server)安全加固方案

状态:设计文档,未实施。本文只给出方案与落地顺序,不含代码改动。 前提:SlimeWorks 当前只在个人/局域网环境使用、不分发公网,因此本节所有条目均为 「需要知道 + 择机加固」,而不是「立即修复」。请不要把本文中的条目当作已实现能力。


1. 现状事实(已核对代码)

项 现状 位置
监听地址 硬编码 0.0.0.0(IPv4 全网卡),端口用户可配 lib/core/services/node/node_settings_service.dart:277
服务端实现 手写 raw TCP HTTP/1.1 服务器,线程模型为「一连接一线程」,上限 30 rust/src/node_server/mod.rs:83、:660、:680
鉴权 完全没有:无 token、无配对、无来源校验 rust/src/node_server/mod.rs:163-560 路由表
CORS 所有响应固定 Access-Control-Allow-Origin: *,OPTIONS 预检允许 GET/POST/DELETE mod.rs:220、:564、:577
本地文件读取 GET /node/media?path=... 直接对参数路径 File::open,无根目录白名单 rust/src/node_server/media_handler.rs:79、serve_media_file_with_range()
凭据回显 GET /manga/token 无鉴权返回 PC 侧 Manga 登录态 rust/src/node_server/mod.rs:274
写操作 POST /node/call 按 action 分发到各模块(含删除、导入、重命名等写路径) rust/src/node_server/mod.rs:171 → handlers::dispatch_action
日志面 /sentry/events/delete_batch、/sentry/projects/rename、/sentry/export 均无鉴权 rust/src/node_server/mod.rs:455/473/495
请求体读取 有 10 秒读超时,但 没有 Content-Length 上限:vec![0u8; content_length] 直接按客户端声明的头部值分配 mod.rs:106、:135-156
上传 POST /node/upload 是未实现的桩,固定返回错误(因此当前不构成写盘面) rust/src/node_server/handlers.rs:1074
TLS 明文 HTTP;移动端通过 HTTP 中转 PC 节点 同上
证书校验关闭 danger_accept_invalid_certs(true) rust/manga_module/src/client.rs:973、rust/power_stats/src/client.rs:18(rust/game_library/src/api.rs:1408 已显式为 false)
iOS ATS NSAllowsArbitraryLoads 开启,允许任意 http 明文 ios/Runner/Info.plist:87
根证书安装 capture_proxy 用 echo '<pwd>' \| /usr/bin/sudo -S security add-trusted-cert ... 拼 shell 串 rust/capture_proxy/src/cert.rs:283
.env 被列进 pubspec.yaml assets 随包分发;内容仅含窗口尺寸与 ffmpeg 下载 URL,无凭据,且已 gitignore pubspec.yaml:139、.gitignore:50
CI 密钥 PGYER key 走 $(正确),但以 -F "_api_key=..." 形式出现在命令行,构建机上 ps 可见 .github/workflows/release.yml:415-427

说明:.env 与 PGYER 两项经复核不构成凭据泄露,属于低优先级的打包/进程可见性卫生问题, 之前审计中的「密钥泄露」表述需要按本表修正。


2. 风险与影响(局域网内、无鉴权的前提下)

  1. 同网段任意设备可读取本机任意文件:/node/media?path=/Users/x/.ssh/id_rsa 只要路径可猜/可遍历即可读取, 不受媒体库范围限制。这是本项目最实质的一条。
  2. 任意模块写操作:/node/call 可调用注册过的 action,包含删除集合/删除事件/导入等,等价于 「局域网内任何人拿到你媒体库的读写权」。
  3. 凭据外带:/manga/token 让任意局域网设备取走 Manga 登录态并冒用账号。
  4. 浏览器侧间接利用:所有响应带 Access-Control-Allow-Origin: *,浏览器里的任意网页既能发出这些请求, 也能读到响应内容(不限于「简单请求」,预检也被允许)。用户访问恶意网页时,§2 的 1/2/3 可被远程网页驱动完成。
  5. 无界内存分配:Content-Length 由客户端声明且无上限,服务端先 vec![0u8; content_length] 再读体, 一个声明超大长度的 POST 就能把 PC 端连接线程打到 OOM(10 秒读超时救不了已经完成的分配)。 属于可用性风险;吞吐/并发侧的性能问题已单独处理(见 §5)。

3. 加固方案(分四层,可独立启用)

L1 收敛监听面(改动最小,收益最大)

L2 配对 + 请求签名(跨端协议变更,需要移动端与 PC 端同时升级)

L3 资源范围最小化(防「任意文件」与「凭据外带」)

L4 传输与依赖卫生(可选,成本较高)


4. 建议落地顺序

  1. L1(半天,无协议变更)→ 立刻消除「默认全网卡」暴露。
  2. L3 的路径白名单与 /manga/token 重做(1 天,无协议变更)→ 消除 §2 中最实的 1、3 两条。
  3. L2 配对 + 签名(2-3 天,含移动端改造与兼容窗口)→ 之后才谈「可以给同事/另一台设备开外网访问」。
  4. L3 能力开关(0.5 天)。
  5. L4 按需排期,不与前三项绑定。

兼容窗口建议:L2 上线后保留 1 个大版本的「免鉴权局域网模式」开关,并在 UI 上标红提示风险, 避免移动端旧版本直接不可用;到期后移除。


5. 与性能改动的边界(本分支已完成,避免混淆)

以下属于本轮性能修复,不代表安全问题已解决:

安全侧仍待处理的本质问题只有一句话:任何能连到该端口的人 = 你的媒体库读 + 大部分模块写。 唯一已经落地的是授权码校验(见 §6),它把「能连就能读」改成了「没有授权码就读不到」。


6. 已落地:节点授权码(L2 的简化版)

L2 的完整配对/签名尚未实现,先落了成本最低的一档:共享授权码。

仍不解决的问题:摘要即凭据(拿到码即等同拿到库),无设备维度、无重放保护、无 TLS, 明文 HTTP 下抓取包仍可复用该摘要。要按 §3 L2 升级为配对 + HMAC 签名。


7. 已落地:归档上传端点 POST /node/upload/archive

给「在远程文件夹内拖入本地目录」用的传输通道(rust/src/node_server/upload_handler.rs)。

残留风险(本轮按约定只记录不改码):

  1. dest 仍是调用方给的绝对路径:拿到授权码 = 能往节点机器上任意已存在目录写文件, 没有”只能写进媒体库根目录之下”的白名单(对应 §3 L3)。
  2. 落点存在性检查与写入之间有 TOCTOU 窗口;解压本身也会覆盖同名文件。
  3. 只有单次体积上限,没有按节点/按天的存储配额,也没有速率限制。
  4. 请求体按嗅探出的格式分派(zip/7z/tar…),非 zip 格式的穿越防护依赖各 crate 的默认行为。