重要更新:v1.0.4-preview.3安全与稳定性全方位提升,保障系统可靠性

📅2026/7/12 14:51:10 👁️次浏览
重要更新:v1.0.4-preview.3安全与稳定性全方位提升,保障系统可靠性
[!WARNING]重要安全与稳定性修复v1.0.4-preview.3 聚焦管理端 fetch、登录态绑定、Redis 配额缓存、验证码消费、代理信任边界、用户余额字段兼容、MySQL 老库注册兼容和用户通知设置的安全边界。受影响部署可能在特定管理操作、OAuth 绑定、密码重置/注册、高并发缓存更新、反向代理配置、用户余额调整或旧库用户注册场景中暴露 SSRF、进程 panic、缓存扣减丢失、验证码复用、IP 限流绕过、余额上限异常、弱邮箱校验或注册失败等风险。建议升级后重点验证渠道模型拉取、OAuth/第三方账号绑定、密码重置、用户注册、老 MySQL 数据库上的新用户注册、用户通知邮箱保存、管理端用户余额添加/减少/覆盖、充值码兑换并发处理、模型请求限流、站点与品牌顶部导航设置、模型广场自动链路分组展示以及 Redis 启用时的用户/token 配额缓存更新。[!NOTE]当前 MAX API 分为正式版和 Preview 预览版。Preview 预览版用于提前开放修复和能力便于在真实环境中验证兼容性、稳定性和安全性正式版将在对应 Preview 预览版稳定运行后发布。Release NotesGitHub 地址https://github.com/MAX-API-Next/MAX-API/releases/tag/v1.0.4-preview.3Highlights加固管理端渠道模型拉取接口/api/channel/fetch_models会按系统 fetch setting 执行 SSRF URL 校验并通过受保护 HTTP client 发送请求。修复 OAuth、GitHub、Discord、OIDC、WeChat、LinuxDo、Telegram 和邮箱绑定路径中直接id.(int)的 panic 风险兼容 JSON session 后端反序列化出的数值类型。将 Redis 普通计数和用户/token 配额 hash 增量更新改为 Lua 原子脚本避免 TTL 检查和增量写入之间的 TOCTOU 窗口。收紧通知邮箱格式校验拒绝a、b、.、无点域名和带展示名的非纯邮箱输入。优化充值码兑换的用户级锁生命周期锁在无引用后会从内存 map 清理避免长期运行中按 user id 持续增长。清理controller/channel.go中已迁移到common.*JSON wrapper 的历史 allowlist 条目保持jsonwrapcheck基线与当前代码一致。将“排行榜”可见性和登录要求设置合并到“站点与品牌 - 顶部导航”让顶部导航相关入口在同一个设置区维护。模型广场价格页会展示管理员配置的自动链路分组以及自动路由别名到真实计费分组的处理顺序。加固验证码一次性消费语义密码重置 token 会在校验通过时原子删除注册成功后也会消费邮箱验证码避免同一验证码在有效期内重复提交。收紧默认 trusted proxy 范围仅默认信任 loopback真实反向代理地址需要通过TRUSTED_PROXIES显式配置降低伪造X-Forwarded-For绕过 IP 限流的风险。将用户余额、已用额度和邀请额度相关列升级为bigint并放宽管理端 quota 覆盖和增加操作的 32-bit 上限解除约 4295 显示余额的人工操作天花板。修复 MySQL 老数据库用户注册时命名锁结果扫描可能访问不存在的null_int64表导致注册失败的问题。补强 MySQL 用户注册命名锁释放校验RELEASE_LOCK()返回0或NULL时会明确报错不再被误判为释放成功。修正内存模式下的模型成功率限流失败请求不再消耗“成功请求”预算。限制上游错误响应体读取大小避免异常 upstream 返回超大错误体时占用过多内存。在 model 层拒绝删除 root 用户避免未来绕过 controller 角色检查的调用路径误删最高权限账号。New Features新增 controller 层 session id 解析 helper统一处理int、int64、float64和字符串形式的 session user id。为管理端模型拉取 SSRF 防护新增回归测试覆盖私网base_url被拒绝的场景。为 session 数值兼容、通知邮箱格式和充值锁清理补充聚焦测试。新增验证码“验证并删除”原子 helper供密码重置等一次性凭证场景复用。新增内存限流器只读检查能力用于先判断成功率预算、成功后再记录。新增sql.NullInt64底层 SQL 行扫描回归测试覆盖 MySQL 注册写锁结果使用的1和NULL返回值。新增 MySQL 命名锁成功状态判断测试覆盖1、0和NULL三类返回值。顶部导航设置同屏管理“模型广场”和“排行榜”的展示与登录要求保存时分别更新HeaderNavModules与RankingsModule。模型广场分组筛选新增自动链路说明卡片帮助管理员和用户理解auto_routes的真实计费分组链路。为 trusted proxy 默认值、验证码一次性消费、模型成功率限流、root 用户删除保护和上游错误体截断补充回归测试。ImprovementsFetchModels拼接/v1/models前会裁剪base_url末尾斜杠避免生成重复斜杠路径。FetchModels在读取响应前提前注册defer response.Body.Close()错误状态码也会释放响应体。Redis 增量脚本保留原有语义只有仍存在且带 TTL 的缓存 key 才会更新并在同一 Redis 脚本中保留剩余 TTL。OAuth 绑定类接口在 session id 缺失或格式异常时返回 API 错误不再让类型断言 panic 影响进程稳定性。通知邮箱保存会先裁剪首尾空白再以标准邮箱地址入库减少用户输入差异。密码重置 token 在密码写库前完成消费即使后续 DB 操作失败也不会留下可被并发复用的重置凭证。注册流程在用户创建成功后消费邮箱验证码保持验证码与实际注册结果一致。访问旧的/system-settings/site/rankings设置路径会自动跳转到/system-settings/site/header-navigation避免历史书签进入无效设置页。顶部导航保存现在并行提交普通导航模块和排行榜模块并统一走设置页错误处理。模型广场自动链路展示会过滤空分组、重复分组和当前列表不可见分组并在没有auto_routes配置时继续使用 legacyauto_groupsfallback。MySQL 邮箱归一化写锁改为通过database/sql的Row().Scan接收GET_LOCK/RELEASE_LOCK结果避免 GORM 将sql.NullInt64误当作模型表解析。MySQL 命名锁获取和释放共用同一个成功状态判断只有返回有效的1才视为锁操作成功。模型成功率限流不再使用临时计数 key 试探容量避免失败请求污染成功预算。MySQL/PostgreSQL 启动迁移会检查用户余额相关列的现有类型只在仍为非bigint时执行列类型调整SQLite 保持原有兼容路径。root 用户删除保护下沉到model.User.Delete/HardDelete调用方只传 user id 时会先读取现有角色再判断。上游错误响应超过 1 MiB 时会截断并在错误信息中标记保留排障上下文同时控制内存占用。Bug Fixes修复管理端FetchModels接收请求体base_url后使用裸http.Client{}直接请求绕过项目 SSRF 防护的问题。修复FetchModels使用直接json.NewDecoder以及渠道密钥处理和 Ollama 拉取流中直接json.Marshal/json.Unmarshal的项目规范偏离问题。修复第三方账号绑定路径中 session id 如果由 JSON 后端恢复为float64会触发 panic 的问题。修复邮箱绑定路径同样直接断言 session id可能在异常 session 类型下 panic 的问题。修复 RedisRedisIncr在 TTL 读取后 key 状态变化时可能静默跳过计数更新的问题。修复用户/token 配额缓存使用的RedisHIncrBy存在同类 TTL TOCTOU 竞态的问题。修复通知邮箱只检查是否包含导致明显非法邮箱也能保存的问题。修复topUpLocks按 user id 缓存锁对象且不清理长期运行后可能持续增长的问题。修复密码重置 token 先校验、后删除导致两个并发请求可能同时通过校验并写入不同新密码的问题。修复注册成功后邮箱验证码仍保留到过期时间可能被同一邮箱在窗口期内重复提交的问题。修复“排行榜”设置与顶部导航设置分散在不同站点子页导致管理员配置顶部导航入口时需要跨区维护的问题。修复模型广场把缺失的auto_routes当成空数组后旧版auto_groups自动分组 fallback 可能不再展示的问题。修复使用老 MySQL 数据库注册用户时GORM 扫描sql.NullInt64可能生成null_int64表查询并返回Error 1146 (42S02): Table ...null_int64 doesnt exist的问题。修复 MySQLRELEASE_LOCK()返回0或NULL时仍被当作释放成功的问题避免锁状态异常被静默吞掉。修复管理端用户余额在数据库列仍为INT时最终 quota 可能受2147483647quota units约 4295 显示余额限制的问题。修复用户余额、已用额度和邀请额度相关字段在 MySQL/PostgreSQL 老库中仍保留INT类型导致高额度用户可能被截断、溢出或写入失败的问题。修复默认 trusted proxy 包含 RFC-1918 / link-local 私网段时处在受信任私网来源的请求可伪造X-Forwarded-For绕过 IP 限流的问题。修复内存模式下模型成功率限流把失败请求也计入成功请求预算的问题。修复上游错误响应体直接io.ReadAll异常 upstream 返回超大错误体时可能放大内存占用的问题。修复model.Delete/HardDelete只依赖 controller 层保护未来直接调用 model 方法时可能误删 root 用户的问题。Compatibility Notes管理端模型拉取现在受全局 fetch setting 约束。若部署确实需要从私网地址或非默认端口拉取模型列表需要在 fetch setting 中显式允许对应私网 IP、IP 白名单或端口。Redis 配额缓存更新仍只作用于带 TTL 的缓存 key无 TTL 或不存在的 key 继续保持不更新避免改变缓存写入边界。OAuth/session id 解析现在接受 JSON session 常见的float64数值但仍拒绝空值、零值和无法解析的类型。通知邮箱现在要求纯邮箱地址带展示名的Name userexample.com不再接受用户应保存userexample.com。充值码兑换仍保持同一用户并发互斥锁清理只发生在当前引用全部释放后不改变并发拒绝语义。验证码和密码重置 token 现在是一旦成功校验即消费用户重复提交同一验证码或重复打开同一重置链接会得到无效结果需要重新发起验证码或重置邮件。MySQL 部署不需要、也不应新增null_int64表该错误来自注册写锁结果扫描方式升级代码并重启后即可消除对应假表查询。如果 MySQL 报告命名锁未被当前连接持有或锁状态不可确认注册写锁释放会返回显式错误这类情况应按连接或数据库锁状态异常排查而不是忽略。默认TrustedProxies不再包含私网网段。部署在 Nginx、Cloudflare Tunnel、内网 LB 或容器网络后方时应通过TRUSTED_PROXIES配置真实代理 IP/CIDR否则ClientIP()会回退到直接连接来源。“站点与品牌 - 排行榜”旧 section 会重定向到“站点与品牌 - 顶部导航”外部脚本或书签若直接打开旧 URL建议更新到/system-settings/site/header-navigation。底层RankingsModuleoption 仍独立保存。模型广场只展示当前可见分组内的自动链路未配置auto_routes的部署继续使用旧auto_groupsfallback。管理端 quota 覆盖仍拒绝负数quota 增加仍必须为正数MySQL/PostgreSQL 老库会在启动迁移中把users.quota、users.used_quota、users.aff_quota和users.aff_history转为bigint不再以2147483647quota units 作为业务上限。升级前建议备份数据库并确认这些列没有自定义类型约束。内存模式的模型成功率限制现在只统计 HTTP 状态码小于 400 的请求如果部署依赖失败请求也消耗成功预算需要改用总请求数限制表达该策略。上游错误响应体超过 1 MiB 时不会完整保留在错误信息和调试日志中排查异常 upstream 时可结合上游日志查看完整错误体。root 用户无法通过 model 层软删除或硬删除需要处理最高权限账号时应先降级或迁移 root 身份。Verificationgo test ./controller -run Test(FetchModelsRejectsPrivateBaseURL|SessionUserID|NormalizeNotificationEmail|TopUpLock|UpdateUserSettingPreservesUnrelatedSettings|AdminReset.*RequiresPaymentCompliance)$go test ./controller -run TestQuotaBoundsValidation -count1go test ./model -run TestScanNullableInt64UsesSQLRow|TestMySQLNamedLockResultSuccessRequiresOne|TestInsertRejectsConcurrentDuplicateEmailMySQL -vgo test ./model -run Test(UserUpdateDoesNotOverwriteAccountingFields|UserUpdatePersistsZeroValueProfileFields|UserUpdateDoesNotClearEmailFromLoadedPartialUpdate|UpdateUserSettingOnlyUpdatesSetting)$ -count1go test ./model -run Test(WaitPendingLogQuotaDataDrainsEnqueuedWork|EnqueueLogQuotaDataAfterShutdownPersistsSynchronously|Record.*QuotaDataAsync)$go test ./relay/helper -run TestStreamScanner|TestNewStreamScanner|Test.*Pingcd web/default bun run typecheckgo test ./commongo test ./middlewarego test ./servicego test ./modelgo test ./controllergo test ./common ./controller ./model ./relay/helpergo run ./tools/jsonwrapcheckgit diff --checkFull Changelog: https://github.com/MAX-API-Next/MAX-API/compare/v1.0.4-preview.2…v1.0.4-preview.3