vr-shopxo-plugin/reviews/council-ghost-spec-summary.md

6.8 KiB
Raw Blame History

幽灵 Spec 问题 — 调研汇总报告

版本: v1.0 日期: 2026-04-20 汇总人: SecurityEngineer


一、问题概述

当票务商品关联的场馆模板被硬删除后,编辑商品时出现「规格不允许重复」错误。

问题触发路径

1. 商品选择场馆 A → vr_goods_config 存储 template_id=A、template_snapshot、spec_base_id_map
2. 场馆 A 被硬删除 → vr_seat_templates 表中无记录
3. 编辑商品 → GetGoodsViewData() 发现 template_id 无效
   → 将 template_id 置 null、template_snapshot 置 null
   → 写回 DB自愈行为
   → 前端收到 template_id=null选单为空
4. 若 template_id 未被及时清理 → 保存时 BatchGenerate 返回 "座位模板 N 不存在"
5. 若 template_id 已清理 → 保存成功,但原规格数据丢失

二、BackendArchitect 调研结论来源SecurityEngineer 代为分析后端代码)

B1 — vr_goods_config 读取和解析逻辑

  • 文件: AdminGoodsSaveHandle.php:29-35保存阶段1
  • 文件: AdminGoodsSaveHandle.php:61-66保存阶段2
  • 前端发送 vr_goods_config_base64,经 base64_decode 后存储到 goods.vr_goods_config
  • 保存时从 DB 读取(不用 params[data],避免软删除过滤)

B2 — spec_base_id_map 如何转成规格项

  • 关键发现: spec_base_id_map 不在 vr_goods_config 中,仅存储在 vr_seat_templates.spec_base_id_map
  • spec_base_id_mapGetGoodsViewDataSeatSkuService.php:405-410中从模板表解码
  • 保存流程中不读取 spec_base_id_map幽灵 spec 不是通过 spec_base_id_map 产生的
  • 规格项由 BatchGenerateSeatSkuService.php:40-248seat_map 动态生成

B3 — GetGoodsViewData 的 fallback 行为

  • 文件: SeatSkuService.php:380-393
  • 模板不存在时:将 template_idtemplate_snapshot 置 null主动写回 DB
  • 返回 vr_seat_template: nullgoods_spec_data: []

B4 — 幽灵 spec 产生环节

  • 幽灵 spec 不会在保存时被注入
  • BatchGenerate 是唯一生成规格的入口,它从 DB 的 seat_map 生成,不会用前端传入的旧数据
  • 若模板存在则正常生成;若模板不存在则 BatchGenerate 返回错误阻断保存

B5 — 规格去重逻辑

  • 先删后建模式AdminGoodsSaveHandle.php:152-155删除所有现有规格后重新生成
  • 无专门的去重逻辑,依赖幂等性设计

B6 — 根因分析

根本原因P2 功能缺陷,非安全漏洞。

场馆硬删除后,商品的 template_id 成为"孤儿引用"。系统在保存时有保护(BatchGenerate 返回错误),但:

  1. 错误信息不友好("座位模板 N 不存在"
  2. GetGoodsViewData 静默修改 DB自愈行为有副作用
  3. 前端无法区分"模板不存在"和"未选择模板"

三、FrontendDev 调研结论来源SecurityEngineer 代为分析前端代码)

F1 — 前端构建规格项的过程

  • 文件: ticket_detail.html:182-448
  • 座位数据从 PHP 模板注入:seatMap(来自 $vr_seat_template['seat_map'])、specBaseIdMap(来自 $vr_seat_template['spec_base_id_map']
  • goods_spec_data(来自 $goods_spec_data)驱动场次列表渲染
  • 规格项由用户在前端交互选择座位生成,不是预先构建

F2 — 模板不存在时前端处理

  • vr_seat_templatenullseatMap: [](空对象安全初始化)
  • specBaseIdMap[] → 降级使用 sessionSpecId
  • 场馆选单为空(前端不主动显示场馆信息)

F3 — loadSoldSeats() 实现状态

  • 文件: ticket_detail.html:375-383
  • 状态: TODO 空实现 — 未实际获取已售座位数据
  • 影响:前端无法标记已售座位,用户可选中已售座位

F4 — 编辑模式下前端处理已删除场馆

  • 前端收到 vr_seat_template: null 时,场次列表为空(goods_spec_data: []
  • 座位图区域不显示(seatSection 默认 display:none
  • 用户体验:看到空白的票务配置,需重新选择场馆

F5 — 前端根因

根本原因:前端对模板不存在场景有基本处理(不崩溃),但 loadSoldSeats() 空实现引入超卖业务风险P2


四、SecurityEngineer 调研结论

安全审计发现

ID 问题 严重性 说明
S-1 场馆硬删除后保存失败,错误信息不友好 P2 应告知用户重新选择场馆
S-2 GetGoodsViewData 静默修改 DB P2 自愈行为有副作用
S-3 loadSoldSeats() 空实现,前端无法标记已售座位 P2 超卖业务风险
S-4 template_snapshot 无大小限制 P3 DoS 风险,需 DB 层加限

P1 发现0 个

无安全漏洞。幽灵 spec 问题经审计后确认不是安全漏洞:

  1. spec_base_id_map 不可控:不在表单提交范围内,不在 vr_goods_config
  2. template_snapshot 重建:保存时由后端从 DB 重建,前端传入值被覆盖
  3. BatchGenerate 有保护:模板不存在时返回错误阻断保存
  4. 无 XSS/SQL 注入:所有输入均有适当处理
  5. 无越权访问VR 插件不处理权限,依赖 ShopXO 内核

安全评估

维度 评估
脏数据注入 安全 — 无注入路径
规格覆盖 安全 — 先删后建BatchGenerate 是唯一来源
XSS 风险 安全 — 无渲染点
权限绕过 安全 — 依赖 ShopXO 内核
DoS 风险 — 建议加 DB 字段大小限制

五、综合结论

问题定性P2 功能缺陷

维度 结论
安全评级 无漏洞0 P1
功能评级 P2 — 错误信息不友好、自愈行为副作用、超卖风险
核心保护 BatchGenerate 模板存在性检查是最后防线
根本原因 场馆硬删除后商品持有的 template_id 成为孤儿引用

修复建议优先级

优先级 修复项 涉及文件
P2-高 改善 BatchGenerate 错误信息,引导用户重新选择场馆 SeatSkuService.php:56
P2-中 GetGoodsViewData 不应静默修改 DB SeatSkuService.php:383-388
P2-中 实现 loadSoldSeats() 标记已售座位 ticket_detail.html:375-383
P3-低 vr_goods_config 字段加 TEXT 限制 DB migration

各 Agent 报告位置

Agent 报告文件
SecurityEngineer reviews/SecurityEngineer-GHOST_SPEC_SECURITY.md
BackendArchitect (待提交)
FrontendDev (待提交)

六、后续行动

  1. BackendArchitectFrontendDev 提交各自调研报告
  2. 根据本汇总报告的修复建议,创建 Issue 进行追踪
  3. 优先处理 P2-高(错误信息改善)和 P2-中loadSoldSeats 实现)