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

161 lines
6.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 幽灵 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_map``GetGoodsViewData`SeatSkuService.php:405-410中从模板表解码
- 保存流程中不读取 `spec_base_id_map`**幽灵 spec 不是通过 spec_base_id_map 产生的**
- 规格项由 `BatchGenerate`SeatSkuService.php:40-248`seat_map` 动态生成
### B3 — GetGoodsViewData 的 fallback 行为
- **文件**: `SeatSkuService.php:380-393`
- 模板不存在时:将 `template_id``template_snapshot` 置 null**主动写回 DB**
- 返回 `vr_seat_template: null``goods_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_template``null``seatMap: []`(空对象安全初始化)
- `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. **BackendArchitect****FrontendDev** 提交各自调研报告
2. 根据本汇总报告的修复建议,创建 Issue 进行追踪
3. 优先处理 P2-高(错误信息改善)和 P2-中loadSoldSeats 实现)