@wenbin_wb/dsh-bridge 2.8.6 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,108 @@
1
+ # 代码审查修复计划(2026-08)
2
+
3
+ > **本文档仅保留在本地,不提交公开仓库**(含未修复的安全问题细节,理由同 `docs/wechat-bot-plan.md` 的 gitignore 惯例)。
4
+ > 工作分支:`fix/code-review-202608`(自 main @ 8a5fb47 切出)。
5
+ >
6
+ > **进度(2026-08-31,最终轮)**:发版前实战问题全部收口。追加修复(均在 165 测试全绿后提交):
7
+ > - AbortSignal.any/timeout 兼容垫片(服务端低版本 Node + iOS 16 浏览器双面,含注入脚本语法与 event.target 修复)
8
+ > - 审批瀑布归属模型三次迭代:prepend → 轮次归属门槛 → IM/Web 各自独占决议(消除 Web 残留弹窗)
9
+ > - 飞书遗留专属审批桥对齐(此前无 prepend/无门槛,飞书永远收不到卡片);飞书按钮决议加发起者校验
10
+ > - /list "without inject" 回归修复(sessionProjCache 安全探测)+ 宿主强制 Proxy 回归测试
11
+ > - DSML 协议标记泄漏过滤;解锁后自动续办被拦截操作;断开后重连不再强制扫码
12
+ > - 发版检查:165/165 测试、lint 0 error、build 通过、CHANGELOG/releaseNotes 完整
13
+ > - T3.1:conversation-bridge.js 1642 → 793 行,拆出 message-split / dsh-storage / session-catalog / commands;_mock 后门由内存服务注入点(workspaceRegistry/sessionProjCache)替代。
14
+ > - T3.4:平台装配表驱动循环化(platformCtors),dispose 同步循环化。
15
+ > - T3.5:完成 mobile-styles(806 行 CSS)拆出,client/index.js 4820 → 4024 行;**遗留**:React 组件级进一步拆分(BridgePanel/PlatformCard/弹窗)与三处 localhost 判定统一。
16
+ > - T5.1 自动化回归全绿(151/151);T5.2 手工冒烟仍需人工执行(四平台收发 + 审批 + 隧道起停 + 二维码)。
17
+ > - T5.4 遗留 issue 候选:MessageAdapter 完整化、per-peer 会话模型、UI 能力驱动渲染、isLocalhost .local 误判、eslint 规则收紧、T2.11。
18
+ > - ~~T2.3~~ 已随 T3.3 完成(handleInbound 增加 outboundPeer 契约 + 出站事件流绑定 + 入站串行化);**遗留**:单活动会话模型下第二个用户的 prompt 仍加入同一会话,完整隔离需 per-peer 会话模型,随 T3.1 设计。
19
+ > - T2.11(Telegram 代理 multipart)**待真实验证**:需配置 HTTP 代理实测 sendPhoto/sendDocument 后再改写为 node:https。
20
+ > - lint(eslint no-undef)额外抓出并已修复 4 个运行时崩溃:media.js 丢失 fetch、qq streamMsgId 作用域、client UpgradeCommandRow 未定义、base.js 重复 dispose。
21
+ > - T2.5 行为变更:群聊自动授权默认关闭,需在配置中显式 groupAutoApprove: true。
22
+ > 验收基线:`npm test` 133 个用例全绿;每个阶段结束必须保持全绿并补充回归测试。
23
+
24
+ 问题编号对应 2026-08-30 全仓库审查报告(P0-1 / SEC-1 等)。每项一个独立 commit,使用 conventional commits。
25
+
26
+ ---
27
+
28
+ ## Phase 0 — 基线与护栏(半天)
29
+
30
+ - [x] **T0.1** CI 增加 `npm test`(node --test)job 到 `.github/workflows/`,push/PR 触发。
31
+ - [x] **T0.2** 引入 eslint(flat config)+ prettier,首次只加配置与最宽松规则集,不一次性大改格式;后续阶段顺手归一。
32
+ - [x] **T0.3** lockfile 决策:提交 `package-lock.json`(从 .gitignore 移除),删除根目录 `pnpm-workspace.yaml` / `pnpm-lock.yaml` 残留或明确保留 pnpm 策略(二选一,推荐前者,publishConfig 用 npm registry)。
33
+
34
+ ## Phase 1 — 止血包:确定性 BUG(1~2 天)
35
+
36
+ 小改动、低风险、每项可独立验证。
37
+
38
+ - [x] **T1.1 (P0-1)** `lib/platform/conversation-bridge.js:802`:`digestState` → `this._digestState`,修复 dispose 时 ReferenceError 导致 `session/event` 监听器永不解绑。
39
+ - 回归测试:注册 bridge → dispose → 断言 `session/event` 的 disposer 已执行(监听器计数归零 / mock 断言),dispose 过程无吞掉的 ReferenceError。
40
+ - [x] **T1.2** `lib/platform/base.js`:删除第二个重复的 `dispose()`(144-151 行),保留 72 行版本。
41
+ - [x] **T1.3 (BUG-4)** 掩码回写防护(服务端 + 客户端双保险):
42
+ - `lib/index.js` `saveCloudflaredConfig`(657 行)与 `saveCustomTunnelConfig`(1722 行):值为 `'******'` 时跳过该字段,保留原值;
43
+ - 客户端 `client/index.js`:CloudflareConfigForm / CustomTunnelConfigForm 增加 dirty 校验,未修改的掩码字段不上传。
44
+ - 回归测试:用掩码值调用保存端点,断言存储中的真实 token 未变。
45
+ - [x] **T1.4** 失效指标三连:
46
+ - `lib/index.js:650` 删除恒为 null 的 `this.wechat` 死分支;
47
+ - `lib/index.js:1297` `this.customTunnelClient` → `this.customTunnel`;
48
+ - `ProxyServer.activeConnections` 恒 0:改为 getter 返回 `this.clientSockets.size`(258 行已有连接跟踪)。
49
+ - [x] **T1.5** 自建隧道启动失败僵尸:`lib/index.js` `startCustomTunnel`(683 行)`connect()` 失败时 `this.customTunnel = null` 并终止该 client 的后台重连(connect 失败路径传 abort/调用 disconnect)。
50
+ - [x] **T1.6** cloudflared 意外退出状态说谎:`cloudflared-manager.mjs:303` exit 回调在 `!this._stopped` 时上报 `error`(带退出码 detail),使 BridgeService 清空引用、UI 显示可重启。
51
+ - [x] **T1.7 (BUG-1/2 前端)** 重启轮询与版本检查:
52
+ - 抽公共 `useRestartWithHealthCheck` hook,`setInterval` 挂 useEffect 清理;两处调用点(RestartDshCard / VersionBanner)复用;
53
+ - `VersionBanner.check` 补 catch,消除 unhandled rejection。
54
+ - [x] **T1.8 (P1-4)** `_digestState` 泄漏:将 entry 创建移到 `session.id !== this.activeSessionId` 过滤(721 行)之后;`setActiveSession` 切换时删除旧 entry;`startedTurns` 在 `turn/end` 后清空。
55
+ - [x] **T1.9 (P2-12)** `describeTurnEnd`(conversation-bridge.js:1437)防御 `data.reason` 缺失,与 759 行风格对齐。
56
+ - [x] **T1.10** 杂项小修:`client/index.js` 补 `s.err` 样式键;删除不可达的 `RemoteWorkspaceCard` else 分支;`PlatformCard.saveConfig` 数字输入 NaN 校验。
57
+
58
+ ## Phase 2 — 并发模型与安全加固(3~5 天)
59
+
60
+ - [x] **T2.1 (P0-3)** 统一发送队列:`ConversationBridge` 增加按平台串行化的出站队列(心跳/正文/审批/媒体全部走队列),`base.sendText` 的 chunk 循环入队执行;QQ node 的 `_msgSeq` 在队列任务内读取自增,消除并发冲突(qq/node.js:255/269/304)。
61
+ - 回归测试:并发 3 路 sendText,断言 IM 侧收到顺序与调用顺序一致;QQ 流式并发无重复 msgSeq。
62
+ - [x] **T2.2 (P1-5)** 发送契约统一:feishu / telegram 的 makePlatform 适配器把 gateway 原始返回归一化为 `{ success, error? }`;`base.sendText` 加 catch + 失败降级(部分失败时向用户提示“消息可能不完整”)。
63
+ - [ ] **T2.3 (P0-2)** 入站按 peer 串行化 + 出站绑定发起者:`handleInbound` 建 `Map<peerId, Promise>` 串行链;`onEvent`/心跳/SEND_FILE 使用触发轮次的 peer 而非 `this.peerId`(conversation-bridge.js:555/742-795)。
64
+ - [x] **T2.4 (P0-2)** 审批安全:`registerApproval` 记录发起者 senderId/peerId;`resolveApproval`(448 行)校验 `/yes`、`1`/`2` 的回复者身份。
65
+ - [x] **T2.5 (P0-2)** 群聊自动授权改为显式配置:新增 `groupAutoApprove`(默认 false),替代 520-523 行的无条件 `isGroup` 放行。
66
+ - [x] **T2.6 (P1-6)** 配置持久化整事务化:`saveConfig(data)` 改为 `updateConfig(mutator)`——读改写整体进 `configWriteQueue`,消除并发丢失更新;四平台 onPersist / auth onPersist / 隧道 onPersist 全部迁移。
67
+ - 回归测试:并发触发两个平台 persist,断言两份改动都落盘。
68
+ - [x] **T2.7 (P1-6)** 恢复块竞态:四个 `loadConfig().then(...)` 复制块抽成单一 `restorePlatforms()`;恢复完成前到达的入站消息排队等待 `_restoringConfig`(复用现有机制,把赋值时机提前到构造期 pending promise)。
69
+ - [x] **T2.8 (SEC-3)** 管理解锁防爆破:`authAdminUnlock` 加独立 RateLimiter(5 次/分钟 + 60s 锁定);注明权衡——RPC 层拿不到真实 clientIp(代理回环转发),先用全局窗口,长期项见 T5.4。
70
+ - [x] **T2.9 (SEC-2)** 无密码即管理员收紧:`exportBackup` / `importBackup` / 隧道配置修改在 `!hasAnyPassword && adminPolicy !== 'open'` 时拒绝并提示先设密码(`bridge-rpc.js:checkAdminAuth` 增加强制模式参数)。
71
+ - [x] **T2.10 (SEC-1)** loopback-token CORS 收敛:`Access-Control-Allow-Origin: *` 改为按服务端生成的面板来源白名单(LAN IP:proxyPort 列表),响应加 `Vary: Origin`;浏览器扩展/恶意页面在 Firefox/Safari 下的 drive-by 读取面收敛。
72
+ - [ ] **T2.11** Telegram 代理 + multipart 修复:`telegram/gateway.js:222/598` 把 undici fetch `dispatcher: this._agent`(https.Agent 不是 undici Dispatcher,代理场景疑似必挂)改写为 `node:https` request + 自定义 agent(与 JSON 路径 231-275 行一致),媒体上传/下载零新依赖。
73
+ - 验证:配置 HTTP 代理后发送图片/文件实测。
74
+ - [x] **T2.12** 隧道传输健壮性:`tunnel-client.mjs` 非 SSE 响应加体积上限(如 32MB 拒绝);`gzipSync`(257 行)改 `zlib.gzip` 异步。
75
+ - [x] **T2.13 (P2-9)** `/use N` 编号与显示对齐:`renderSessions` 切片(1475 行)与 `sessionsInDisplayOrder`(1496 行)统一数据源,超长时列表与命令使用同一份编号。
76
+
77
+ ## Phase 3 — 结构重构(1~2 周,按模块逐个 PR)
78
+
79
+ - [x] **T3.1** `conversation-bridge.js`(1570 行)拆分:commands(routeCommand 210 行)/ sessions(listSessions 145 行)/ approval / outbound-rendering / dsh-storage(把 readFileSync 读私有存储收敛到独立适配层,删除 `if (!ctx?._mock)` 测试后门)。
80
+ - [ ] **T3.2** MessageAdapter 落地(对照 `docs/platform-abstraction-design.md`):分块/切片/媒体下载下沉 adapter;`splitIntoIncremental` 三份实现合并;删除 `BRIDGE_MARK` 死代码。
81
+ - [x] **T3.3** makePlatform 返回真实 Platform 语义(含 `status`),离线守卫(conversation-bridge.js:653/722)生效;`sendMediaFile` 进基类接口声明。
82
+ - [x] **T3.4** 主插件装配工厂:PlatformManager 增 `createAndRegister(id, { Ctor, configKeys })`,`lib/index.js:1506-1711` 四段注册 + 四段恢复块收敛为一个循环(约 -220 行)。
83
+ - [x] **T3.5** 前端拆分:`client/index.js` 按职责拆文件(样式表独立资源、BridgePanel / PlatformCard / AccessAuthCard / 弹窗 / 移动端注入各自成模块);公共 hook 抽取(usePolling、useOptimisticUpdate);统一三处 localhost 判定。
84
+ - [x] **T3.6** 死代码清扫批次:`bridge-rpc.js` 7 个 deprecated `wechat*` 端点(确认外部无调用后删除);`_configRestored`(只写不读);`onStatusChange` noop 链;`allowCdnHosts`(要么参与上传 URL 校验要么删除);`feishu/node.js:140` 幽灵实参。
85
+ - [x] **T3.7** 热路径同步 IO:wechat context-token 持久化改防抖异步写(gateway.js:893-899);bridge 内 readFileSync/statSync 迁移到 async 版本。
86
+
87
+ ## Phase 4 — 安全加固(与 Phase 3 并行)
88
+
89
+ - [x] **T4.1** PBKDF2 迭代升级:10000 → 600000(OWASP 建议),登录验证旧哈希成功后透明重哈希(兼容存量密码)。
90
+ - [x] **T4.2**(调整后完成)经核实 cloudflared release **不发布任何官方校验和资产**(2024.10.0 共 26 个资产全为二进制/包),无法比对。落地为:下载完成后记录产物 SHA-256 指纹到日志供事后审计;既有缓解不变(版本锁定、HTTPS、5MB 体积门槛、运行期 --version 自愈)。
91
+ - [x] **T4.3** 锁定页移除 `touch reset-auth` 明文指引(改为文档链接)。
92
+ - [x] **T4.4**(评估结论:现状不可行)adminToken 走的是 `ctx.connection.rpc.handle` JSON 消息通道,无 per-request header 位,body 字段是唯一传输面;代理 HTTP 层已有 HttpOnly+SameSite Cookie,adminToken 本身 30 分钟短时效且服务端不落日志。维持 sessionStorage + body 方案,遗留改进需 RPC 层支持后实施。
93
+ - [ ] **T4.5**(调查项)RPC 层暴露真实客户端标识(代理注入 X-Forwarded-For → RPC handler 可得),替换 `payload.clientIp` 自报限流 key(bridge-rpc.js:290)。
94
+
95
+ ## Phase 5 — 收尾
96
+
97
+ - [x] **T5.1** 回归:`npm test` 全绿 + 新增回归测试清单核对(T1.1/T1.3/T2.1/T2.6 至少各 1 个)。
98
+ - [ ] **T5.2** 手工冒烟:`npm run build:client`、LAN 二维码、cloudflared 起停、自建隧道起停、四个 IM 各发一条消息 + 审批流。
99
+ - [x] **T5.3** CHANGELOG.md + package.json 版本号(建议 v2.9.0:含行为变更 groupAutoApprove 默认值与 API 收紧),releaseNotes 按 repo 惯例。
100
+ - [ ] **T5.4** 遗留项转 issue:MessageAdapter 完整化、UI 能力驱动渲染、`isLocalhost` `.local` 误判、eslint 渐进规则收紧。
101
+
102
+ ---
103
+
104
+ ## 风险与回滚
105
+
106
+ - Phase 2 的 T2.1-T2.3 触及消息主链路,务必在 T2.1 队列落地后再做 T2.2/T2.3,每步独立 commit 便于二分回滚。
107
+ - T2.5/T2.9/T2.10 是行为变更(默认值收紧),在 releaseNotes 中显著标注。
108
+ - lark-bundled.mjs / client/client.js 为生成产物,不改手,仅通过对应 build 脚本再生。