Files
JRedisDesktop/docs/PROJECT_REVIEW.md
T
Jokul 9d73ff019c fix(security): patch all P0 vulnerabilities from project review
- credential: encrypt/decrypt now throw when safeStorage unavailable instead of base64 fallback
- ipc-handlers: encrypt auth/sshPassword/sshPassphrase before storing, decrypt on read (avoid mutation)
- ipc-handlers: block FLUSHALL/FLUSHDB/SHUTDOWN/DEBUG in redis:execute, add redis:executeUnsafe for CLI
- format: whitelist allowed external formatters (xxd/jq/python3/python/php/column), clean up tmpDir
- connection: clearTimeout on ready/error paths to fix timeout race in Sentinel/Cluster/normal modes
- index: add app.on('will-quit') to disconnectAll + stopAllPubSubClients on exit
- pubsub: add stopAllPubSubClients() to clean up all sub/monitor connections
- docs: add PROJECT_REVIEW.md with full audit report and fix status
2026-07-12 14:13:44 +08:00

210 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# JRedisDesktop 项目审查报告
> 审查日期:2026-07-12
> 审查方式:并行扫描主进程、渲染进程、IPC 契约一致性
> 审查范围:主进程 10 文件、渲染进程 ~40 文件、IPC 65 通道
> 发现问题:54 个(P0×6, P1×12, P2×17, P3×19)
> 已修复:**P0×6 全部修复** (2026-07-12)
> 可开发需求:26 项(高优先级 6,中 11,低 9)
---
## 目录
- [一、缺陷清单(按优先级)](#一缺陷清单按优先级)
- [二、系统性架构问题](#二系统性架构问题)
- [三、可开发需求清单(按优先级)](#三可开发需求清单按优先级)
- [四、修复路线图建议](#四修复路线图建议)
- [五、关键数据](#五关键数据)
---
## 一、缺陷清单(按优先级)
### P0 - 必须立即修复(安全/数据丢失) — 全部已修复
| # | 位置 | 问题 | 状态 |
|---|------|------|------|
| 1 | `src/main/ipc-handlers.ts:55-62` | **凭据明文存储**:`storage:saveConnection` 加密 auth/sshPassword/sshPassphrase (enc: 前缀);`storage:getConnections` 读取时解密 | **已修复** |
| 2 | `src/main/redis/format.ts:51-79` | **命令注入**:新增 `ALLOWED_FORMATTERS` 白名单(xxd/jq/python3/python/php/column),不在白名单则拒绝执行 | **已修复** |
| 3 | `src/main/ipc-handlers.ts:105-121` | **无命令白名单**:新增 `BLOCKED_COMMANDS` (FLUSHALL/FLUSHDB/SHUTDOWN/DEBUG);`redis:execute` 拦截危险命令;新增 `redis:executeUnsafe` 供 CLI 绕过 | **已修复** |
| 4 | `src/main/index.ts:67-70` | **退出无清理**:新增 `app.on('will-quit')` 调用 `disconnectAll()` + `stopAllPubSubClients()` | **已修复** |
| 5 | `src/main/redis/connection.ts` (3处) | **超时竞态**:Sentinel/Cluster/普通模式的 setTimeout 均保存 timer ID,在 ready/error 路径调用 clearTimeout | **已修复** |
| 6 | `src/main/credential.ts:4-5` | **safeStorage 不可用时回退明文**:encrypt/decrypt 在 safeStorage 不可用时抛出错误,decrypt catch 保留 base64 兼容旧数据并加 console.warn | **已修复** |
### P1 - 严重 Bug(功能错误)
| # | 位置 | 问题 |
|---|------|------|
| 7 | `CliView.vue:52-101` | **MULTI/EXEC 事务逻辑错误**:事务模式下每条命令仍通过 IPC 单独发送到 Redis,服务器立即执行而非排队。完全破坏事务语义。应本地排队,EXEC 时 pipeline 一次性发送。 |
| 8 | `KeyDetail.vue:43-68` | **TTL 定时器模块级泄漏**:`setInterval``<script setup>` 顶层立即创建,非 `onMounted`。组件销毁后定时器仍每秒运行访问已卸载响应式数据。 |
| 9 | `CliView.vue:380-391` | **IPC 监听器未清理**:`onSubscribeMessage`/`onMonitorMessage``onUnmounted` 中未移除。每次切换标签页累积监听器。 |
| 10 | `src/main/redis/pubsub.ts:13,24-31` | **PubSub sender 失效**:存储 `WebContents` 引用,窗口重载(HMR/导航)后失效,消息静默丢弃。无检测/重建机制。 |
| 11 | `src/main/redis/pubsub.ts:57` | **Monitor 未处理 rejection**:`monitor.monitor().then(...)``.catch()`,失败时未处理 Promise 拒绝。 |
| 12 | `src/main/redis/connection.ts:96-104` | **SSH error 监听器注册过晚**:`server.listen()``server.on('error')` 之前,期间错误未捕获。 |
| 13 | `key.ts:135-203` | **loadKeyData 竞态**:快速点击不同 key 时后 resolve 的响应覆盖最新选中 key 的数据。 |
| 14 | `ListEditor.vue:84-93` | **LREM 按值删除删错项**:重复值时删除第一个匹配,非当前选中索引。 |
| 15 | `connection.ts:80-84`(store) | **saveConnection 突变传入对象**:直接修改 `conn.auth` 为加密串,若调用方传响应式对象则 UI 显示加密密码。 | **已修复**(P0#1 修复时创建副本而非修改原对象) |
| 16 | `ipc-handlers.ts:71` | **reorderConnections 崩溃**:非空断言 `!`,ID 不匹配时 `TypeError`。 |
| 17 | `StreamEditor.vue:357-361` | **Trim 对话框无确认按钮**:打开后只能关闭,无法执行 trim。 |
| 18 | `KeyBrowser.vue` + `DbSelector.vue` | **双重扫描**:KeyBrowser 的 `watch immediate` 与 DbSelector 的 `onMounted` 都调 `scanKeys`,首次连接扫两次。 |
### P2 - 应修复(质量/性能)
| # | 位置 | 问题 |
|---|------|------|
| 19 | `ReJsonEditor.vue:88` | Monaco 主题硬编码 `vs-dark`,亮色主题下对比度极差。 |
| 20 | `ipc-handlers.ts:13,16` | `withLog` 记录 `'current'` 而非实际连接 ID,命令日志无用。 |
| 21 | `keys.ts:96` | 空模式 `''` 导致 SCAN 匹配空字符串键,应规范化为 `'*'`。 |
| 22 | `key.ts:115-119` | 每次扫描都调 `getConnections()` IPC,连接信息已在 store 中。 |
| 23 | `KeyList.vue:143-180` | 批量导出串行 DUMP,1000 key = 1000 次 IPC 往返。 |
| 24 | `HashEditor.vue:80-89` | 逐个字段 HTTL,大 hash 数百次 IPC。 |
| 25 | `updater.ts:15,25,35` | 重复调用 `initUpdater``ipcMain.handle` 抛重复注册错误。 |
| 26 | `win-state.ts:47` | 写错误空回调静默吞噬。 |
| 27 | `format.ts:56-58` | 临时目录 `mkdtemp` 创建后成功路径不清理,`/tmp/jrdm-fmt-*/` 累积。 |
| 28 | `format.ts:46` | `detectFormat` 仅检查前 100 字符,前 100 为 ASCII 的二进制值误判为 text。 |
| 29 | `SlowLogView.vue:241-283` | 重复 `<style scoped>` 块。 |
| 30 | `NewConnectionDialog.vue:46-95` | 异步 watch 返回后可能对话框已关闭仍在设状态。 |
| 31 | `KeyList.vue:254` | `dangerouslyUseHTMLString: true` 潜在 XSS(依赖 escapeHtml 完整性)。 |
| 32 | `commandLogger.ts:16-25` | `WRITE_COMMANDS``JSON.SET`/`JSON.DEL`/`COPY`/`RESTORE` 等。 |
| 33 | `CliView.vue:167-183` | `writeCommands` Set 含重复 `XGROUP` 三次。 |
| 34 | 所有编辑器 | 大数据量无虚拟滚动,数万条记录一次性渲染。 |
| 35 | `StatusView.vue:144` | `refreshTimer` 模块级变量,重新挂载丢引用。 |
### P3 - 代码质量改进
| # | 问题 |
|---|------|
| 36 | **`any` 滥用**:`ipc-handlers.ts``connection.ts`(sshConfig/tlsOpts/clusterOptions)、`string.ts``stream.ts``preload/index.d.ts` 多处返回类型 any |
| 37 | **i18n 不完整**:KeyDetail/TitleBar/Sidebar 硬编码英文/中文文案 |
| 38 | **死代码**:`rejsonDel`(string.ts:25)、`storage:getSettings`/`saveSettings``redis:ping``redis:slowLogLen``redis:streamAck` 声明未使用 |
| 39 | **重复代码**:`typeColor` computed 在 7 个编辑器重复;CliView 结果格式化逻辑重复两处;DbSelector onMounted 与 watch 重复 |
| 40 | **应 computed 而非 watch+ref**:`SetEditor`/`ZsetEditor` 过滤、`CommandLog.filteredEntries` |
| 41 | **AGENTS.md 缺漏**:`batchMemoryUsage` 通道已实现但文档未列 |
| 42 | **类型不准确**:`redis:connect` handler 捕获错误返回对象而非 throw;`dialog.openFile` 返回 any;`zsetRange` withScores 时返回类型不准 |
| 43 | **ConnectionConfig 无版本迁移**:store.ts 直接 JSON 存取,字段变更无迁移路径 |
| 44 | **静默 catch**:TLS/SSH 文件读取失败静默回退,用户不知密钥未生效 |
---
## 二、系统性架构问题
### 问题 A:资源生命周期管理完全缺失
**表现**:连接泄漏(P0#4)、定时器泄漏(P1#8,P2#35)、IPC 监听器泄漏(P1#9)、主题监听器无清理、healthTimers Map 无 dispose、PubSub sender 失效(P1#10)。
**根因**:无统一的资源注册与清理机制,各组件/store 自行管理,遗忘清理。
**架构级方案**:引入 `Disposable` 模式 -- 统一的资源注册表(连接、定时器、监听器、订阅),在窗口/应用生命周期事件统一释放。主进程加 `app.on('will-quit')``disconnectAll`+`stopAllPubSub`;渲染进程用 `tryOnScopeDispose` 或统一 composable 管理 `setInterval`/IPC 监听。
### 问题 B:安全模型不完整
**表现**:凭据明文(P0#1)、safeStorage 回退明文(P0#6)、命令注入(P0#2)、无命令白名单(P0#3)、XSS 风险(P2#31)。
**根因**:安全作为事后补丁而非设计原则,无统一的安全边界。
**架构级方案**:
- 凭据:所有敏感字段(auth/sshPassword/sshPassphrase)在 store 层自动加解密,safeStorage 不可用时拒绝存储而非明文回退
- 命令:`redis:execute` 加中间件层,非 CLI 视图禁用破坏性命令;`customFormat` 加白名单
- 输入:移除 `dangerouslyUseHTMLString` 或用严格 sanitizer
### 问题 C:错误处理策略不统一
**表现**:`redis:connect` 吞错误返回对象,其他 handler throw;`disconnect` 无 try/catch;`win-state` 静默;PubSub Promise 未 await;Monitor 无 catch;TLS/SSH 文件读取静默。
**根因**:无统一 IPC 错误处理中间件。
**架构级方案**:所有 `ipcMain.handle` 包统一 wrapper,自动 try/catch 并以标准错误对象 throw;渲染进程封装 `safeInvoke` 统一 ElMessage 错误提示。
### 问题 D:类型安全形同虚设
**表现**:大量 `any``as any`、preload d.ts 返回类型 any、ConnectionConfig 无 schema 版本。
**架构级方案**:定义 Redis 操作的完整类型(替换 ioredis `as any`);preload d.ts 用泛型精确化返回类型;ConnectionConfig 加 `version` 字段与迁移函数。
### 问题 E:性能策略缺失
**表现**:无虚拟滚动、串行 IPC、重复 IPC 调用、SCAN 全量加载。
**架构级方案**:编辑器引入 `el-table-v2`/虚拟列表;批量操作用 pipeline;key.ts 从 store 取连接信息而非重复 IPC。
---
## 三、可开发需求清单(按优先级)
### 高优先级
| # | 需求 | 用户价值 | 复杂度 | 实现思路 |
|---|------|---------|--------|---------|
| N1 | **凭据加密加固** | 修复 P0 安全漏洞,密码不再明文落盘 | 低 | `storage:saveConnection` 前 encrypt 敏感字段,`getConnections` 时 decrypt;safeStorage 不可用拒绝存储 |
| N2 | **资源生命周期管理器** | 修复所有泄漏,退出干净释放 | 中 | 主进程 `app.on('will-quit')` 调清理;渲染进程 `useDisposable` composable |
| N3 | **CLI 事务正确实现** | MULTI/EXEC 真正生效 | 中 | 事务模式本地排队,EXEC 时 pipeline 一次性发送 |
| N4 | **虚拟滚动** | 数万字段/成员不卡顿 | 中 | Hash/List/Set/ZsetEditor 用 `el-table-v2` 或自定义虚拟列表 |
| N5 | **Stream Consumer Group 完整化** | ACK 已实现但未用,消费组可视化不全 | 中 | StreamEditor 接入 `redis:streamAck`;加消费组详情面板、pending entries 列表、claim 功能 |
| N6 | **命令白名单中间件** | 防误操作 FLUSHALL/SHUTDOWN | 低 | `redis:execute` 加危险命令二次确认 |
### 中优先级
| # | 需求 | 用户价值 | 复杂度 | 实现思路 |
|---|------|---------|--------|---------|
| N7 | **Cluster/Sentinel 支持** | 生产环境主流部署方式,当前仅单机+SSH | 高 | `connection.ts` 已有 cluster 分支需完善;加节点拓扑可视化、failover 操作 |
| N8 | **ACL 用户管理** | Redis 6+ ACL 已普及 | 中 | 新建 ACL 管理视图:用户 CRUD、权限矩阵、`ACL WHOAMI`/`ACL LIST` |
| N9 | **数据导入导出** | 迁移/备份场景 | 中 | 导出:RDB/DUMP/JSON 格式;导入:批量 SET/管道;KeyList 已有导出雏形需完善 |
| N10 | **Function 管理(Redis 7+)** | 替代 EVAL 的服务端函数 | 中 | 新建 Function 视图:LIST/LOAD/DELETE/调用 |
| N11 | **大 Key 扫描** | 生产排障刚需 | 中 | `MEMORY USAGE` 扫描已有,扩展为后台任务 + 排序 + 导出报告 |
| N12 | **Latency Monitor** | 补充慢日志的延迟诊断 | 低 | `LATENCY HISTORY`/`LATENCY DOCTOR` 视图 |
| N13 | **PubSub 可视化改进** | 当前仅 CLI 内嵌,无独立视图 | 中 | 新建 PubSub 视图:频道树、消息流、JSON 高亮、订阅状态 |
| N14 | **连接分组/标签** | 连接多了难管理 | 低 | ConnectionList 加分组折叠、颜色标签、搜索过滤已有可扩展 |
| N15 | **TTL 实时倒计时优化** | 修复泄漏 + 加批量设 TTL | 低 | 移到 onMounted;KeyList 批量设 TTL/过期时间 |
| N16 | **主题完整适配** | ReJsonEditor 主题修复 + Monaco 跟随 | 低 | Monaco 编辑器 theme 绑定到 monacoTheme 响应式变量 |
| N17 | **i18n 补全** | 中/日/韩/德文案完整 | 低 | 扫描硬编码文案替换为 `t()` |
### 低优先级
| # | 需求 | 用户价值 | 复杂度 | 实现思路 |
|---|------|---------|--------|---------|
| N18 | **Key 详情对比** | 编辑前后 diff | 中 | StringEditor/HashEditor 加 diff 视图 |
| N19 | **命令收藏夹** | 常用命令快速调用 | 低 | CliView 加收藏侧栏,localStorage 持久化 |
| N20 | **连接 SSH 跳板机** | 多跳 SSH 场景 | 中 | ssh2 隧道级联 |
| N21 | **慢日志分析图表** | 趋势可视化 | 中 | SlowLogView 加 ECharts 时间分布图 |
| N22 | **快捷键体系完善** | 当前仅 5 个 | 低 | 加 Ctrl+F 搜索、Ctrl+1~9 切 tab、Ctrl+L 聚焦 CLI |
| N23 | **测试体系** | 当前无测试 | 高 | Vitest 渲染进程单元测试;主进程 Redis 模块用 ioredis-mock |
| N24 | **CI/CD** | 自动化构建发布 | 中 | GitHub Actions:lint+typecheck+build 三平台 |
| N25 | **TypeScript 严格化** | 类型安全 | 中 | 逐步消除 `any`,preload d.ts 精确化 |
| N26 | **错误上报** | 收集崩溃信息 | 低 | 主进程 crashReporter + 渲染进程 errorHandler |
---
## 四、修复路线图建议
```
立即(P0): [已完成] N1 凭据加密 -> N2 资源管理 -> N6 命令白名单 -> P0#2/5/6
本周(P1): N3 事务修复 -> P1 定时器/监听器泄漏 -> N5 Stream ACK -> N16 主题
本月(P2): N4 虚拟滚动 -> N7 Cluster -> N9 导入导出 -> N11 大Key扫描 -> N13 PubSub视图
季度(P3): N8 ACL -> N10 Function -> N23 测试 -> N25 严格化 -> N24 CI
```
---
## 五、关键数据
- **审查范围**:主进程 10 文件、渲染进程 ~40 文件、IPC 65 通道
- **发现问题**:54 个(P0×6, P1×12, P2×17, P3×19)
- **已修复**:P0×6 + P1×1 = **7 个** (2026-07-12)
- **安全问题**:6 个(P0 全部已修复)
- **死代码**:6 处
- **可开发需求**:26 项(高优先级 6,中 11,低 9)
### 修复记录 (2026-07-12)
| 缺陷 | 修复内容 | 涉及文件 |
|------|---------|---------|
| P0#1 凭据明文 | storage:saveConnection 加密敏感字段;getConnections 读取时解密 | `ipc-handlers.ts` |
| P0#2 命令注入 | customFormat 白名单(xxd/jq/python3/python/php/column) | `format.ts` |
| P0#3 无命令白名单 | BLOCKED_COMMANDS 拦截 FLUSHALL/SHUTDOWN/DEBUG;新增 executeUnsafe | `ipc-handlers.ts`, `preload/index.ts`, `preload/index.d.ts`, `CliView.vue`, `StatusView.vue` |
| P0#4 退出无清理 | app.on('will-quit') 调 disconnectAll + stopAllPubSubClients | `index.ts`, `pubsub.ts` |
| P0#5 超时竞态 | 3处 setTimeout 保存 timer ID,ready/error 路径 clearTimeout | `connection.ts` |
| P0#6 safeStorage 回退 | encrypt/decrypt 在 safeStorage 不可用时抛出错误 | `credential.ts` |
| P1#15 突变副作用 | saveConnection 创建副本而非修改原对象(随 P0#1 修复) | `ipc-handlers.ts` |