Skip to content

调度、VNC、上游开通、续费与插件链路审查修复(2026-08-13)

  • 文档性质:针对定时任务、Hooks、VNC 中继、上游开通、续费与插件体系的专项审查结论、修复记录与遗留待办。
  • 审查方式:5 个并行审查子代理深挖 + 主代理直接阅读调度/Hooks/插件任务核心链路;关键高危项逐一亲验(判定 CONFIRMED/PLAUSIBLE)。
  • 审查结论:共确认 25+ 项发现(高危 9 项)。本次已完成 P0/P1/P2 共 10 项修复并附回归测试;遗留中低危待办 10 项,不在本批次排期。

背景

项目 2026-08-12 全量专家团审查后,本批次聚焦六个功能域:定时任务(Scheduler/Heartbeat)、调度 Hook、VNC 中继、对接上游开通(provisioning)、续费(renewal)与插件体系(plugins)。目标是在既有"后端专家团审查修复计划"之外,补齐这些链路的资金安全、并发幂等与凭据/越权边界。

已修复项(2026-08-13,均含回归测试)

#模块级别问题修复验证
1VNCP0上游连接失败日志把含 VNC 密码的完整 path 明文落库VncRelayCommand::maskUpstreamPath() 日志侧只保留路径段、剥离 queryBackendHealthFixRegressionTest 断言 path 脱敏;VNC 相关测试全过
2开通P0services:sync-upstream 对纯门面反射不存在的方法,命令必然全量失败改为注入 ServiceDetailService 直接调用公开方法新增回归断言防反射错类复发
3续费P0上游续费崩溃/超时窗口内重试重复提交 /host/renew,二次扣供应商余额renew_inflight 幂等标记:调用前落 pending_submit,拿到账单 ID 立即升级 submitted 并落库;重试识别残留 inflight 不重提,未知账单号转人工核实新增 2 个 inflight 定向测试,续费 18 项回归通过
4续费P0自动续费与手动续费同周期重叠双扣建单入口拦截"服务未过期 + 同一续费窗口内已履约"新增同周期拦截测试,续费回归全过
5开通P1队列履约与手动重试并发时,旧内存值导致二次开通购物车锁内用 $service->fresh() 复查幂等并传入插件;锁内 assertNoUnresolvedUpstreamProvisionInvoice 复查开通 15 项回归通过
6调度P1service-status-sync(全量)与插件任务重复同步 ZJMF全量任务动态排除 ZJMF(插件任务注册时),插件禁用回退全量兜底ServiceStatusSyncBindingTest 通过
7调度管理P2retryFailedRun 重跑前不回收陈旧活跃运行,可被过期记录阻塞重跑前 reclaimStaleRunsForTask(与 dispatchLocked 对齐)调度管理 5 项回归通过
8调度管理P2manual_retry 子运行派发失败成死胡同工作区已实现 requeueDispatchFailedRun 重派;本批次验证并修复测试 tearDown 残留清理清理后连续两次全过
9插件P1插件 Provider 在 boot 期可无约束注册系统级路由/调度/全局中间件PluginProviderRegistry::activate 前后对比路由/调度/中间件数量,违规抛异常;enable() 入口回滚禁用新增 2 个拦截测试,插件 38 项回归通过
10开通P1ZjmfBillingRestoreService 物理删除全部财务数据并清零余额空库强制审计:目标表非空时非 dry-run 必须显式 --force,否则拒绝;dry-run 输出覆盖统计新增拒绝测试,恢复命令测试 4 项通过

第二轮遗留项处理(2026-08-13)

#模块级别处理
1开通JWT 缓存 TTL 钳制:剩余有效期不足时不再缓存失效会话(ZjmfAuthManager),消除"缓存已过期 token 必然先 401 再重登"
2开通syncServiceStatuses 返回值剥离会话 JWT(ZjmfStatusService),避免调用方整体记日志/缓存时泄露上游会话
3续费/调度nextduedate 兼容 Y-m-d 日期字符串(ServiceRenewService/ServiceStatusSyncService),避免与上游延期口径漂移
4开通幂等回查校验上游账单已付(ZjmfProvisionService):host 存在但账单未付时重试余额支付,避免重试绕过支付直接确认造成漏收
5插件测试发送接口加节流(v2-admin.phpthrottle:10,1),防止对任意收件人短信/邮件轰炸

以上均通过相关回归测试(73 passed)。

第三轮处理(2026-08-13)

#模块级别处理
1工程债清理 ServiceRenewService/ProvisionService 的 PHPStan nullsafe/is_array 报错(Order 模型 invoice() 补泛型 PHPDoc、isAdminManualOrder/hostname 规则去掉冗余 is_array、log 访问改类型化写法),并修正 baseline ignore 计数;三个文件 PHPStan 全绿
2VNCrelay SSRF 纵深防护(VncRelayCommand::assertPublicUpstreamTarget):解析目标 host 并拒绝内网/保留地址(含 IPv6),上游或 Redis 受控时不再可把 relay 导向内网 TCP 桥
3VNCrelay 防 DoS 加固:并发连接上限 512、握手 15s / 空闲 300s 超时、单侧缓冲上限 1MB,逐轮清理挂起连接;relay token 窗口由 120s 缩短至 60s(ServiceVncService

以上均通过相关回归测试(80 passed)。全项目 PHPStan 仍有约 411 个既有错误(工作区大量未提交改动导致 baseline 整体过时),需在改动收敛后统一重新生成 baseline,另立批次。

第四轮处理(2026-08-13)

#模块级别处理
1插件manifest 的 entry/provider/定时任务/hook 监听器类必须位于插件目录内(PluginInstaller::assertPluginClassInsideDirectory),manifest 被污染时不再可实例化/调用任意已加载类
2VNCVNC 密码不再明文落 volatile Redis:launch token 与 relay token 载荷移除 password,密码仅经 vnc_credentials 返回给当前认证用户供本地 noVNC 认证

以上均通过相关回归测试(115 passed)。

第五轮处理(2026-08-13,按用户决策执行)

#模块级别处理
1VNC兑换端点 Origin 纵深校验(ServiceConsoleController::assertVncTokenOriginAllowed):请求带 Origin 且不在 console/api 白名单时拒绝;无 Origin(同源/非浏览器)放行不误伤。附 Feature 测试
2开通上游会话 Cookie 后端读取端支持加密存储:UpstreamBindingWriter 支持 web_session_cookie secret(提交或从 notes 迁入加密)、PluginBindingResolver 解密注入、ZjmfFinanceTransport 优先 secret 回退 notes;写入端完整加密待管理端前端配合
3续费尽力对账:ZjmfRenewService 容错提取上游实扣金额(upstream_amount),ServiceRenewService 履约后与本地应收比对,差异 > 0.01 元记 warning 日志(不阻断)
4工程债PHPStan baseline 重新生成(1853 个既有错误记录为新基线),全量分析恢复全绿
5插件轻量篡改检测:PluginScanner 计算清单内容 sha256,PluginInstaller::install 记录到 integration_plugins.manifest_hash(新迁移),加载时比对不一致记 warning(不阻断)。附 2 个测试

VNC 异步连接按用户决策保持现状(连接上限/超时/缓冲/SSRF 已加固),异步重构待架构评审;孤儿 host 自动回收按用户决策采用只读对账 + 人工核实;VNC token 落 URL 按用户决策保持现状,前端排期

以上均通过相关回归测试(97 passed)。

剩余待办(需产品/架构/前端/发布决策)

#模块级别描述建议
1VNC中继单线程阻塞式 connect/握手仍可全局冻结(已加固超时/上限/缓冲/SSRF;兑换已做 Origin 校验)异步连接改造(架构级,用户决策保持现状,待架构评审)
2开通孤儿 host 自动回收(只读对账命令已提供数据)需产品确认清理策略
3VNCtoken 全程走 URL query(兑换端点 Origin 校验已做)需前端 noVNC 流程改造
4插件插件目录完整性/签名校验(类目录约束已做)需发布/分发链路立项

进度

  • [x] 完成六个功能域并行审查并输出发现清单。
  • [x] 完成 10 项修复(P0×4、P1×4、P2×2),每项附定向回归测试。
  • [x] 修复测试基建缺陷:ScheduleTaskRunAdminApiTest tearDown 未清理 manual_retry 子记录导致的共享测试库残留。
  • [x] 适配既有"事务外预判 + 锁内重查"两阶段续费建单:ServiceRenewCouponRegressionTestpreviewOwnedCoupon mock 由 once() 调整为 atLeastOnce(),与两阶段检查一致。
  • [ ] 遗留中低危项待产品/运维确认后另行排期。

决策日志

日期决策原因
2026-08-13续费崩溃窗口采用 renew_inflight 幂等标记而非上游幂等键ZJMF /host/renew 无幂等键支持;标记能识别"已提交但结果未落库"的窗口,宁可转人工核实也不重复扣款
2026-08-13同周期双扣拦截以"服务未过期 + 履约在一个周期自然月内"为条件避免误伤"提前续多个周期"的合法场景;服务已过期的续费属于恢复动作不拦截
2026-08-13ZJMF 重复同步采用"全量任务动态排除"而非"插件去重"Core 全量任务是兜底,不应收缩(插件禁用时回退全量);插件任务保留定向同步职责
2026-08-13插件 Provider 系统级注册用"注册前后数量对比"检测无法可靠回滚已注册路由,但能在启用入口拦截并回滚插件状态;运行时 boot 作为告警防线
2026-08-13BillingRestore 采用"目标表非空 + 必须 --force"而非改造合并逻辑一次性恢复工具,改造合并风险高于收益;--force 显式覆盖 + dry-run 预检输出覆盖量是低成本安全闸门
2026-08-13Relay SSRF 防护新增 allow_private_upstream,默认本地跳过、生产严格本地联调常处代理 fake-ip 环境(Clash TUN),任意域名解析为 198.18/15 与 fc00::/7 ULA 假地址,DNS 内网段校验误杀全部合法 VNC 上游;本地 Relay 仅监听 127.0.0.1,可跳过,生产保持严格拦截,可用 VNC_RELAY_ALLOW_PRIVATE_UPSTREAM 显式覆盖

基于 AGPL-3.0-or-later 发布