From e0b2403486219321de36af3aa343f3247940c88e Mon Sep 17 00:00:00 2001 From: Council Date: Wed, 15 Apr 2026 09:14:29 +0800 Subject: [PATCH 1/2] =?UTF-8?q?council(draft):=20FrontendDev=20-=20Round?= =?UTF-8?q?=201=20vr-shopxo-plugin=20=E4=BB=A3=E7=A0=81=E5=AE=A1=E8=AE=AE?= =?UTF-8?q?=E8=AE=A1=E5=88=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- plan.md | 103 ++++++++++++++++++-------------------------------------- 1 file changed, 33 insertions(+), 70 deletions(-) diff --git a/plan.md b/plan.md index c75cebd..3f2c9ca 100644 --- a/plan.md +++ b/plan.md @@ -1,82 +1,50 @@ -# Council Plan — openclaw-claude-code MiniMax 路由补丁设计 +# Council Plan — vr-shopxo-plugin 代码审议 -> Round 1 — 2026-04-14 -> Branch: council/Architect → main -> 状态:**Draft Phase 完成,待 Review** +> Round 1 — 2026-04-15 +> Branch: council/FrontendDev → main +> 状态:**Draft Phase** --- ## Task Summary -为 `@enderfga/openclaw-claude-code` 插件设计可配置的 MiniMax 路由方案,解决硬编码 `provider: 'anthropic'` 导致路由失效的问题。 - -**核心约束**: -1. 配置优先(不硬编码) -2. 向后兼容(默认走 Anthropic 官方) -3. 可还原(插件更新不被覆盖) -4. 显眼易懂(有注释说明) +对 vr-shopxo-plugin 插件进行全面的代码审议,覆盖插件架构、票务核心、前端页面、数据库 Schema、安全性 5 个维度。**仅评论不改代码**,输出独立评审报告到 `reviews/code-review-FrontendDev.md`。 --- -## 4 Q Discussion (Round 1) +## Review Scope -### Q1 (Backend): proxy handler 如何读取 provider URL 配置? +### 1. 插件架构 +- `app/plugins/vr_ticket/EventListener.php` +- `app/plugins/vr_ticket/plugin.json` +- 生命周期钩子实现、数据库迁移策略、菜单/权限注册 -**Backend 立场**:**B — OpenClaw config** -- 理由:`providers` section 已有 MiniMax 配置示例,符合 OpenClaw 插件生态 -- 插件可通过 `this.config.providers` 读取,无需额外解析逻辑 +### 2. 票务核心 +- `app/plugins/vr_ticket/service/TicketService.php` +- `app/plugins/vr_ticket/service/BaseService.php` +- `onOrderPaid()` 并发问题、`verifyTicket()` 核销漏洞、AES QR 加密强度 -### Q2 (Architect): models.js provider 映射如何支持配置覆盖? +### 3. 前端票务详情页 +- `app/plugins/vr_ticket/view/goods/ticket_detail.html` +- HTML/CSS/JS 质量、座位图渲染逻辑、观演人表单安全性 -**Architect 立场**:**A — 启动时覆盖** -- 理由:不修改 node_modules,通过 OpenClaw hook 在插件加载前注入配置 -- 符合"可还原"原则,插件更新后仍生效 +### 4. 数据库 Schema +- `app/plugins/vr_ticket/database/migrations/001_vr_tables.sql` +- 表结构规范、索引合理性、外键关系 -### Q3 (PM): 配置项放在 OpenClaw config 哪个 section? - -**PM 立场**:新增 `routing` section,结构如下: -```json -{ - "routing": { - // 路由覆盖配置 - "modelProviderOverride": { - // 模型名 → provider 映射 - "claude-sonnet-4-20250514": "minimax-portal", - "claude-opus-4-6": "minimax-portal", - "claude-haiku-4-20250514": "minimax-portal" - }, - // 可选:baseUrl 覆盖(如果 provider 配置的 baseUrl 需要临时覆盖) - "baseUrlOverride": { - "minimax-portal": "https://custom-api.minimaxi.com/v1" - } - } -} -``` - -**理由**: -- `routing` 语义清晰,表示"路由规则" -- `modelProviderOverride` 显式声明哪些模型走哪个 provider -- 放在顶层 `routing` 而非嵌套在 `providers` 里,醒目且独立 -- 向后兼容:不配置则使用默认行为 - -### Q4 (综合): 推荐方案 - -**推荐方案**: -- **配置位置**:`~/.openclaw/openclaw.json` → `routing.modelProviderOverride` -- **读取层**:proxy handler 从 `config.routing` 读取覆盖配置 -- **注入层**:通过 OpenClaw hook 在 plugin 加载前注入配置(不修改 node_modules) -- **回滚步骤**:删除 `routing` 配置项即可还原默认行为 +### 5. 安全性审计 +- SQL 注入、XSS、支付回调重放攻击、QR 票防伪造 --- ## Task Checklist -- [x] A1: Backend Q1 回答 - provider URL 读取方式 -- [x] A2: Architect Q2 回答 - provider 映射配置覆盖机制 -- [x] A3: PM Q3 回答 - 配置项位置与命名 -- [x] A4: 综合 Q4 回答 - 推荐方案 -- [ ] B1: 交叉评审(各 Agent 互相评审) -- [ ] C1: 最终投票 +- [ ] R1: 评审插件架构 (EventListener.php / plugin.json) +- [ ] R2: 评审票务核心 (TicketService.php / BaseService.php) +- [ ] R3: 评审前端页面 (ticket_detail.html) +- [ ] R4: 评审数据库 Schema (001_vr_tables.sql) +- [ ] R5: 安全性综合审计(注入/XSS/重放/QR伪造) +- [ ] R6: 汇总评审报告 (reviews/code-review-FrontendDev.md) --- @@ -84,9 +52,9 @@ | Phase | 内容 | 状态 | |---|---|---| -| **Draft** | 4 Q 独立回答 + 综合方案 | ✅ Done | -| **Review** | 交叉评审,输出 `reviews/` 文件 | ⏳ Pending | -| **Finalize** | 合并到 main,投票 | ⏳ Pending | +| **Draft** | 各维度代码阅读 + 问题识别 | ⏳ Pending | +| **Review** | 输出完整评审报告 | ⏳ Pending | +| **Finalize** | 提交报告到 main | ⏳ Pending | --- @@ -94,13 +62,8 @@ | Task | Owner | Status | |---|---|---| -| A1: Q1 回答 | council/Backend | `[Done]` | -| A2: Q2 回答 | council/Architect | `[Done]` | -| A3: Q3 回答 | council/PM | `[Done]` | -| A4: 综合结论 | council/Architect | `[Done]` | -| B1: 交叉评审 | council/All | `[Pending]` | -| C1: 最终投票 | council/All | `[Pending]` | +| R1-R6: 完整评审 | council/FrontendDev | `[Claimed: council/FrontendDev]` | --- -**[CONSENSUS: NO]** — Draft 完成,等待 Review 轮 \ No newline at end of file +**[CONSENSUS: NO]** — Round 1 规划完成,待执行审议 From 826a39f610d206dc05e00574f45e3ef480d8fd51 Mon Sep 17 00:00:00 2001 From: Council Date: Wed, 15 Apr 2026 09:24:04 +0800 Subject: [PATCH 2/2] =?UTF-8?q?council(review):=20FrontendDev=20-=20?= =?UTF-8?q?=E5=AE=8C=E6=88=90=20vr-shopxo-plugin=20=E5=89=8D=E7=AB=AF?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E8=AF=84=E5=AE=A1=E6=8A=A5=E5=91=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 评审发现:2个严重(S-01价格篡改/S-02 XSS)、4个中等、3个轻微、4项建议 交叉确认:与 SecurityEngineer / BackendArchitect 报告高度一致 Co-Authored-By: Claude Sonnet 4.6 --- plan.md | 31 +- reviews/code-review-FrontendDev.md | 479 +++++++++++++++++++++++++++++ 2 files changed, 500 insertions(+), 10 deletions(-) create mode 100644 reviews/code-review-FrontendDev.md diff --git a/plan.md b/plan.md index 3f2c9ca..71247af 100644 --- a/plan.md +++ b/plan.md @@ -39,12 +39,12 @@ ## Task Checklist -- [ ] R1: 评审插件架构 (EventListener.php / plugin.json) -- [ ] R2: 评审票务核心 (TicketService.php / BaseService.php) -- [ ] R3: 评审前端页面 (ticket_detail.html) -- [ ] R4: 评审数据库 Schema (001_vr_tables.sql) -- [ ] R5: 安全性综合审计(注入/XSS/重放/QR伪造) -- [ ] R6: 汇总评审报告 (reviews/code-review-FrontendDev.md) +- [x] R1: 评审插件架构 (EventListener.php / plugin.json) +- [x] R2: 评审票务核心 (TicketService.php / BaseService.php) +- [x] R3: 评审前端页面 (ticket_detail.html) +- [x] R4: 评审数据库 Schema (001_vr_tables.sql) +- [x] R5: 安全性综合审计(注入/XSS/重放/QR伪造) +- [x] R6: 汇总评审报告 (reviews/code-review-FrontendDev.md) --- @@ -52,9 +52,20 @@ | Phase | 内容 | 状态 | |---|---|---| -| **Draft** | 各维度代码阅读 + 问题识别 | ⏳ Pending | -| **Review** | 输出完整评审报告 | ⏳ Pending | -| **Finalize** | 提交报告到 main | ⏳ Pending | +| **Draft** | 各维度代码阅读 + 问题识别 | ✅ 完成 | +| **Review** | 输出完整评审报告 | ✅ 完成 | +| **Finalize** | 提交报告到 main | ⏳ 待合并 | + +--- + +## 问题发现汇总 + +| 严重度 | 数量 | 典型问题 | +|--------|------|---------| +| 🔴 严重 | 2 | 购票参数前端计算无验签 / `$goods.content\|raw` XSS | +| 🟡 中等 | 4 | loadSoldSeats 未实现 / CSS 无响应式 / 座位图渲染边界 / JSON输出XSS | +| 🟢 轻微 | 3 | 已选座位 UI 无状态管理 / 观演人表单无校验 / 座位映射数据泄露 | +| 💡 建议 | 4 | 座位字符集ASCII限制 / 座位数无上限 / spec_base_id缺索引 / 地图JSON无长度限制 | --- @@ -62,7 +73,7 @@ | Task | Owner | Status | |---|---|---| -| R1-R6: 完整评审 | council/FrontendDev | `[Claimed: council/FrontendDev]` | +| R1-R6: 完整评审 | council/FrontendDev | `[Done: council/FrontendDev]` | --- diff --git a/reviews/code-review-FrontendDev.md b/reviews/code-review-FrontendDev.md new file mode 100644 index 0000000..691445b --- /dev/null +++ b/reviews/code-review-FrontendDev.md @@ -0,0 +1,479 @@ +# vr-shopxo-plugin 前端代码评审报告 + +> 评审人:FrontendDev +> 日期:2026-04-15 +> 视角:HTML/CSS/JS 质量 / 座位图渲染逻辑 / 响应式设计 / 用户体验 / 观演人表单安全 +> 交叉参考:已合并 SecurityEngineer 和 BackendArchitect 报告,两者发现高度一致,以下从前端视角补充独立发现 + +--- + +## 一、执行摘要 + +vr-shopxo-plugin 的票务详情页(ticket_detail.html)承担了座位选择、场次切换、观演人信息收集等核心交互。作为用户购票流程的唯一入口,其代码质量直接影响用户体验和系统安全性。 + +经过全面评审,发现**2 个严重前端问题、4 个中等问题、5 项改进建议**。最关键的是**购票参数前端计算无服务端验签**,可导致价格篡改攻击;座位图渲染存在未处理的边界情况,CSS 缺乏响应式适配,移动端体验较差。 + +--- + +## 二、票务详情页(ticket_detail.html)评审 + +### 2.1 🔴 严重 — 购票参数前端计算,价格可被篡改 + +**位置:** 第 384-422 行 `submit()` 函数 + +```javascript +var checkoutUrl = this.requestUrl + '?s=index/buy/index' + + '&goods_params=' + encodeURIComponent(goodsParams); +location.href = checkoutUrl; +``` + +**问题分析:** + +整个购票参数(goods_id、spec_base_id、stock、extension_data)由前端 JavaScript 计算后拼接 URL 跳转至 ShopXO 结算页。服务端**不重新计算价格**,完全信任客户端数据。 + +攻击者可通过以下步骤以 0.01 元购买任意座位: + +1. 打开浏览器开发者工具 +2. 在控制台执行: +```javascript +// 修改座位价格为 0.01 +vrTicketApp.selectedSeats.forEach(s => s.price = 0.01); +vrTicketApp.submit(); +``` + +3. 服务端收到 `goods_params` 中的 `stock` 和 `extension_data`,直接使用,不验价 + +**影响:** +- 价格篡改漏洞(已由 BackendArchitect 标记,本报告从 JS 层面量化攻击路径) +- 前端座位数量无服务端校验,可超购 +- `extension_data` 中的 `seat_info` 可伪造(客户端直接写入 JSON) + +**修复建议:** +```javascript +// 方案一:改为 POST 请求,服务端验价 +$.post(this.requestUrl + '?s=plugins/vr_ticket/index/create_ticket_order', { + goods_id: this.goodsId, + spec_base_id: this.sessionSpecId, + seats: JSON.stringify(this.selectedSeats), + attendees: JSON.stringify(attendees) +}, function(res) { + if (res.code == 0) { + location.href = res.data.checkout_url; + } +}); + +// 方案二:添加 HMAC 签名 +var payload = JSON.stringify({ + goods_id: this.goodsId, + seats: this.selectedSeats, + timestamp: Date.now() +}); +var sig = CryptoJS.HmacSHA256(payload, clientSecret); +location.href = checkoutUrl + '&sig=' + sig; +``` + +### 2.2 🟡 中等 — 座位图渲染缺乏边界情况处理 + +**位置:** 第 255-282 行 `renderSeatMap()` + +**问题一:座位图数据空值未处理** + +```javascript +map.map.forEach(function(rowStr, rowIndex) { + var chars = rowStr.split(''); + chars.forEach(function(char, colIndex) { + if (char === '_' || char === '-') { + // 空白座位处理 + } else { + var seatInfo = seats[char] || {}; // ⚠️ seats 字典可能为空 + var price = seatInfo.price || 0; // 价格为 0 时无座可买 + // ... + } + }); +}); +``` + +**场景:** 后端 `seat_map` JSON 中 `seats` 字段缺失或为空,则所有字符都映射到空对象 `{}`,价格为 0。用户在 UI 上看到座位,但点击后价格显示 ¥0,提交时服务端可能拒绝或接受零价订单。 + +**问题二:座位类型图例颜色可能不匹配** + +```javascript +sections.forEach(function(sec) { + var color = sec.color || '#409eff'; + legendHtml += '
'+sec.name+'
'; +}); +``` + +图例中的 `sec.color` 直接作为 CSS 背景色,未做颜色格式校验(如 `rgb()`、`hsl()`、十六进制混用)。若数据库中存储了非法 CSS 值,可能破坏布局。 + +**问题三:座位 ID 直接使用字符映射,不安全** + +```javascript +'data-seat-id="'+char+'" ' +``` + +`char` 是座位图字符(如 `A`、`B`),直接作为 `seat-id` 属性值。如果 `char` 包含引号或特殊字符(实际上地图定义中不会出现,但作为防御性编程应转义),可能破坏 HTML 属性边界。 + +**修复建议:** +```javascript +// 1. 座位数据为空时给出明确提示 +if (!map.seats || Object.keys(map.seats).length === 0) { + document.getElementById('seatRows').innerHTML = '
座位图配置错误,请联系管理员
'; + return; +} + +// 2. 价格为零时提示用户 +if (price === 0) { + // 标记为"待定价"座位,禁用点击 + rowsHtml += '
'; +} else { + // 正常渲染 +} + +// 3. seat-id 转义 +var safeSeatId = String(char).replace(/"/g, '"'); +``` + +### 2.3 🟡 中等 — CSS 缺少响应式设计,移动端体验差 + +**位置:** 第 4-118 行 `