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
This commit is contained in:
2026-07-12 14:13:44 +08:00
parent 4d0e0b75aa
commit 9d73ff019c
11 changed files with 314 additions and 19 deletions
+209
View File
@@ -0,0 +1,209 @@
# 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` |