Skip to content

v1.2.0

Choose a tag to compare

@dikxingmengya dikxingmengya released this 13 Sep 04:56
· 175 commits to main since this release

🚀 MisMiss v1.2.0 — 安全加固 · 插件副本隔离修复 · 插件定时消息体系

面板鉴权收紧 · 插件消息与普通消息分类 · 账户副本真正生效 · 插件生命周期补齐


v1.2.0 是一次以修缺陷 + 补能力为主的版本:

  • 收紧了面板的四处认证绕过(其中三处可直接导致 RCE / SSRF / 信息泄露)
  • 修复了 v1.1.0 引入的账户插件副本从未被加载——「副本隔离」此前只对配置/权限/数据成立,对代码不成立
  • 建立了插件定时消息体系:与面板添加的普通消息分类管理,不落盘、不可改删、轮转置顶
  • 补齐插件生命周期:on_livestream_bound 钩子,插件不再需要靠弹幕事件兜底重试

🔒 安全修复

面板中间件此前把若干前缀整段设为公开,导致以下端点无需登录即可调用:

端点 风险 修复
POST /api/config/pip-install 任意 pip install → 任意代码执行 要求管理员登录;拒绝 - 开头的包名(阻断 --target / -r 参数注入),限长并加 --no-input
POST /api/plugin/install* 上传 zip + 裸 extractall → Zip Slip 要求管理员登录;逐成员校验路径,拒绝绝对路径 / .. 段 / 盘符
GET /api/proxy/image 无限制 → SSRF / 开放代理 域名白名单(可在 config.yml 的 proxy.allowed_hosts 扩展)+ 内网地址拦截 + 重定向逐跳校验 + 10MB 上限
WS /api/ws 无鉴权 → 日志泄露 要求管理员 token(浏览器 WebSocket 无法设置 header,故走查询参数);鉴权失败以关闭码 4401 断开,前端据此停止重连
  • 图片代理保持匿名可访问(前端 <img> 无法携带 Authorization header),改以「白名单 + 内网拦截」限制目标
  • token 现在来自查询参数(外部可控),_load_token 增加 64 位十六进制格式校验,堵住文件路径穿越

✨ 新增功能

插件定时消息体系

插件注册的定时消息与面板添加的消息分类管理:

维度 普通消息 插件消息
注册途径 面板「定时消息」页 Plugin.register_timer_message()
持久化 写入账户 state 文件 不写入
编辑 / 删除 / 排序 允许 禁止(但可「跳过」「立即发送」)
轮转位置 队列中段 置顶
执行指针 与插件消息共用同一个 同左
开播条件 无 可声明 only_when_live=True
生命周期 由面板管理 插件停用 / 挂起 / 卸载 / 重载时由框架自动清理
  • 插件消息不落盘,因此插件即使异常终止(terminate 未执行)后重新启用,也不会累积出重复消息
  • 框架在插件激活前与停用/挂起/卸载时按插件名强制回收,不依赖插件自觉
  • 插件消息增删时自动补偿轮转指针,始终指向原来那条消息

插件生命周期:on_livestream_bound

Plugin 新增 on_livestream_bound(livestream) 钩子,在账户新绑定直播间时触发;
插件被启用/重新启用时若账户已绑定,框架会补发一次。
两条路径都覆盖,插件不再需要靠弹幕事件兜底重试注册。

面板

  • 统计区分:账户总览卡片与账户门户概览分别展示「普通 N · 插件 M」
  • 来源筛选:定时消息页可按 全部 / 普通 / 插件 筛选(筛选只影响展示,指针位置按后端索引计算,不会错位)
  • 插件消息在列表中带「插件」徽标,编辑 / 删除 / 排序按钮置灰

🐛 Bug 修复

  • 账户插件副本从未被加载(关键):模块以 import plugins.<dir>.<mod> 导入,而账户目录下的
    plugins/ 是插件数据目录,plugins 又是命名空间包——仓库的 plugins/ 被合并进来并抢先命中,
    于是账户永远执行插件库的源码,installed_plugins/ 下的副本形同虚设,面板「可更新」徽标也失去意义。
    改为按副本绝对路径加载并取唯一模块名,磁盘布局无需迁移
  • 插件异常终止后重复注册定时消息:插件消息此前会随 state 落盘,崩溃后残留 + 重新启用再注册 = 重复条目
  • Server.enable_plugin 复刻了一份 PluginManager 的分支逻辑,导致启用前清理与绑定通知都被跳过
    (表现为禁用后再启用,插件定时消息不再恢复)。改为委托,消除重复实现
  • 合并轮转缓存失效不完整:move_timer_message 是原地交换源列表,缓存持有的是拼接副本不会同步更新
  • _install_lock 形同虚设:在 StreamingResponse 返回前即释放,并发安装互斥失效;改为在生成器内部持锁,
    并给 /install、/install/update 一并加上
  • 嵌套结构的 zip 安装报 500:版本比较按固定根路径读 metadata.yaml,而插件名是递归查找的
  • 插件元数据缺 name 时返回字符串 'None'(真值)导致后续判断失真
  • 无 token 时前端发出字面量 "Bearer null"
  • CHANGELOG 存在性判断错误(3 处):其中一处按 module_path 字符串反推目录,依赖旧的模块命名格式;
    改为在 PluginMetadata 上记录 changelog_path(与既有 readme_path 同款)
  • 日志环形缓冲区容量与文档不符(实例化为 2000,类默认值与文档均为 10000),统一为 10000

🔧 改进优化

  • WebSocket 重连改为真正的指数退避(2/4/8/16/32s)——此前实现为线性,与文档描述不符
  • 合并轮转缓存:_combined_cycle 结果按直播间缓存,避免每次调用新建拼接列表
  • 术语统一:「人工消息」全面更名为「普通消息」(代码 / 文档 / 测试)
  • 死代码清理:3 个无引用的前端组件(InstallModal / RoomSelect / ReadmeModal)、重复定义的路由函数、
    3 个无用 schema、InlineLoader、TYPE_CHECKING 空块、_delete_token、残留的 dashboard.pyc
  • 插件编写约束放宽:各账户插件模块现为独立加载,模块级变量不再跨账户共享;
    原「禁止模块级可写状态」的硬约束改为建议(同一账户内重载仍会重新执行模块代码)

📝 文档

  • README.md — 按多账户模型重写:架构图、事件树(补 LiveQuestionEvent / LiveStatisticsEvent)、
    插件库与账户副本、_ui_schema.json、Docker 部署包与在线更新、实际项目结构
  • docs/TASKS.md — 校正状态(Question 实体、测试体系等已完成;路径统一为 src/core/...)
  • docs/web/README.md — 按实际路由 / 页面 / API 重写,补充认证与授权模型
  • docs/interfaces/interface.md — 定时消息语义更新为三类队列 + 两类消息
  • docs/plugin/PLUGIN_DEV_GUIDE.md — 定时消息章节重写(on_livestream_bound 时机、only_when_live)、
    生命周期钩子表补齐、副本隔离约束修正
  • 5 个插件的 metadata.yaml 与 3 个插件 README 去除已废弃能力的描述

🧪 测试

新增两组回归测试(与既有测试脚本一样保留在本地开发目录,不随仓库发布):

  • 插件定时消息:不落盘、置顶、不可改删移动、按插件清理互不影响、
    指针共用与增删补偿、基类接口、账户未绑定直播间返回空串、
    异常终止后重新启用不产生重复、仅开播时发送条件、计数区分
  • 插件副本隔离:加载账户副本而非插件库源码、副本内相对导入可用、
    各账户独立模块对象、卸载后清理模块缓存

另以 HTTP 端到端复核:未登录访问敏感端点被拒、图片代理拦截内网与非法域名、
插件消息的编辑/删除/移动返回 400 而跳过返回 200、账户插件安装与更新全程加载副本。

既有回归(账户管理 / API 集成 / 授权码 / 跨 worker 状态同步 / 定时持久化)全部保持通过。

⚠️ 升级注意

  • 插件副本开始生效:此前账户实际执行的是插件库源码,升级后会转而执行
    data/accounts/{id}/installed_plugins/ 下的副本。若某账户的副本比插件库旧,
    该账户行为会回退到旧版本——请在账户插件页逐个点「更新」(卡片上有「可更新」徽标)
  • 日志 WebSocket 需要 token:未登录时不再推送,前端会显示「未授权」并停止重连
  • pip 安装与插件上传需要登录(面板用户无需额外操作)
  • 插件定时消息不再写入 server_state.json,历史遗留的插件消息条目在下次保存时自然消失

MisMiss v1.2.0 — 门关严了,副本也真跑起来了 🎧