@wenbin_wb/dsh-bridge 2.10.5 → 2.10.7
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.
- package/CHANGELOG.md +29 -0
- package/README.en.md +410 -410
- package/README.md +432 -432
- package/client/client.js +361 -65
- package/client/index.js +309 -64
- package/docs/CODE_REVIEW.md +220 -0
- package/docs/cloudflare-fixed-domain.md +158 -0
- package/docs/telegram-usage.md +1 -1
- package/lib/cloudflared-manager.mjs +540 -361
- package/lib/compat.js +129 -129
- package/lib/feishu/index.js +225 -225
- package/lib/feishu/node.js +439 -439
- package/lib/index.js +43 -8
- package/lib/platform/base.js +147 -147
- package/lib/platform/commands.js +221 -221
- package/lib/platform/conversation-bridge.js +821 -821
- package/lib/platform/dsh-storage.js +117 -117
- package/lib/platform/index.js +10 -10
- package/lib/platform/message-split.js +229 -229
- package/lib/platform/session-catalog.js +372 -372
- package/lib/platform/stream-slices.js +21 -21
- package/lib/qq/index.js +312 -312
- package/lib/telegram/index.js +216 -216
- package/lib/telegram/node.js +322 -322
- package/lib/wechat/gateway.js +986 -986
- package/lib/wechat/index.js +244 -244
- package/lib/wechat/media.js +285 -285
- package/lib/wechat/node.js +352 -352
- package/package.json +3 -3
- package/docs/fix-plan-202608.md +0 -108
- package/docs/fix-tunnel-sse-and-session-list.md +0 -134
- package/docs/platform-abstraction-design.md +0 -473
- package/docs/wechat-bot-plan.md +0 -294
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# dsh-bridge 代码审查报告(含误报复审)
|
|
2
|
+
|
|
3
|
+
> 审查人:首席架构师视角(重度代码洁癖模式)
|
|
4
|
+
> 范围:`lib/`、`client/`、`test/`、构建/发布脚本与配置
|
|
5
|
+
> 性质:静态全量通读 + 关键路径推理;标注「需实测」的项请发布前验证
|
|
6
|
+
> 版本历史:v2 在 v1 基础上增加「误报复审」——对初版每条结论回源码复核,剔除误报、修正方向性错误、降级不必要事项,并新增复核中发现的真问题。
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 0. 总览
|
|
11
|
+
|
|
12
|
+
项目工程质量高(编码规范、CI ubuntu+windows×node22/24、覆盖广的单测、凭据分离、双语文档)。按严重程度分级:P0 安全、P1 功能 BUG、P2 质量、P3 风格。
|
|
13
|
+
|
|
14
|
+
**初版(v1)共 5 P0 + 9 P1 + 12 P2 + 4 架构项;经误报复审后修正为 3 P0 + 6 P1 + 11 P2 + 4 架构项**,其中:
|
|
15
|
+
- 剔除误报 3 项(v1 P1-5 Telegram 按钮审批、v1 P1-8 客户端 VersionBanner 轮询、v1 P2-8 upgradePlugin 分级)
|
|
16
|
+
- 修正方向 1 项(v1 P1-6 飞书群聊按钮:不是"他人可代批",而是"发起者本人被误拦")
|
|
17
|
+
- 降级 3 项(v1 P0-D hop-by-hop 降 P1;v1 P1-7 getDshVersion 反复 spawn 实为误读、降 P2;v1 P1-8 服务端 checkVersion 无 TTL 部分降 P2)
|
|
18
|
+
- 补实锤并重新定性 1 项(P0-C 隧道浏览器 HTTP/WS 无自身认证,依赖本地认证兜底)
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 1. P0 安全缺陷(复核后 3 项)
|
|
23
|
+
|
|
24
|
+
### P0-A. `AuthManager.verifyRequest` 的 loopback 免认证对「同宿主任意进程」过度信任
|
|
25
|
+
|
|
26
|
+
**位置**:`lib/auth/manager.js:479-494`
|
|
27
|
+
|
|
28
|
+
**现状**:`allowLoopback && isLoopback && !isPublicTunnel` 分支只检查 `host.startsWith('127.0.0.1') || 'localhost' || ''`。
|
|
29
|
+
|
|
30
|
+
**威胁**:`allowLoopback` 默认 true。若 DSH 启用访问认证(`enabled=true`)但尚未设密码/Token,**任何能连到 3082 端口的同宿主进程**(浏览器扩展、恶意软件、同机容器若端口可达)设 `Host: 127.0.0.1:3082` 即以回环身份免认证获得完整访问权,还能调 `/__dsh_bridge__/loopback-token` 直接领 adminToken。
|
|
31
|
+
|
|
32
|
+
**复核结论**:成立。TCP 回环只能证明"来自本机",不能证明"来自本机浏览器/用户";HTTP 层无更强手段。产品若坚持"本机免密",至少应在 UI/文档显著提示;更稳妥是未配置任何凭据时不给 loopback 特殊放行。
|
|
33
|
+
|
|
34
|
+
**对应**:R1。
|
|
35
|
+
|
|
36
|
+
### P0-B. 启用认证但未设密码时,登录等于空密码放行
|
|
37
|
+
|
|
38
|
+
**位置**:`lib/auth/manager.js:367-373`(`verifyPassword` 在 `!hasPassword && mode!=='password_only'` 时返回 success:true)+ 登录 API。
|
|
39
|
+
|
|
40
|
+
**现状**:远程访客只要知道面板地址,POST `/__dsh_bridge__/login` 空密码即签发会话。这是产品"先开放后设密码"设计,但叠加 P0-A/公网隧道时等于无认证。
|
|
41
|
+
|
|
42
|
+
**复核结论**:成立(产品决策需确认),与 P0-A 合并建议。
|
|
43
|
+
|
|
44
|
+
**对应**:并入 R1 讨论。
|
|
45
|
+
|
|
46
|
+
### P0-C. 自建隧道服务端(HTTP 与 WS)均无自身认证,仅依赖本地认证兜底
|
|
47
|
+
|
|
48
|
+
**位置**:`scripts/install-tunnel-server.sh`(内嵌 server.mjs)`:221-302`。
|
|
49
|
+
|
|
50
|
+
**现状**:
|
|
51
|
+
- `/connect`(tunnel client 控制通道):`:306` 校验 `?token=`,**有鉴权** ✓
|
|
52
|
+
- **浏览器 HTTP 转发**(`:221-257`)与 **浏览器 WebSocket 代理**(`:267-302`):只要存在任一在线 tunnel client,**任何公网访客无需 token** 即可把 HTTP/WS 请求转发到本地 DSH(WS 只靠随机 wsId + 10s 超时,HTTP 靠 PATH_PREFIX 隐藏路径)。
|
|
53
|
+
|
|
54
|
+
**威胁评估(关键)**:隧道转发的流量在本地 ProxyServer 会被识别为隧道流量(`x-dsh-internal-tunnel` 头,不享受 loopback 免认证),**真正认证防线在本地 `AuthManager`**。因此:
|
|
55
|
+
- 若用户**启用了访问认证并设密码/Token** → 匿名隧道流量在本地被 401 拦截,P0-C 影响有限(仅暴露登录页);
|
|
56
|
+
- 若用户**未启用认证 / 未设密码**(配合 P0-B)→ 公网任意访客可直达完整 DSH。这与 P0-A/P0-B 同源,属「纵深防御缺口」而非独立可利用漏洞。
|
|
57
|
+
|
|
58
|
+
**复核结论**:成立,定性为**纵深防御缺陷(P0/P1 取决于本地认证配置)**——隧道公网入口无自身认证,把安全完全押注在本地认证开关上;建议服务端加独立访问凭据(或至少 WS 升级校验)。同时主页 `forwardRequest` 转发时透传了浏览器原始 Cookie/头到本地,若本地认证关闭则风险如上。
|
|
59
|
+
|
|
60
|
+
**对应**:R2(服务端加 token/会话校验,禁止匿名浏览器直连;与本地认证形成双保险)。
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## 2. P1 功能 BUG / 明确缺陷(复核后 6 项)
|
|
65
|
+
|
|
66
|
+
### P1-1. 本地 `ProxyServer` 非缓冲转发与 WS 升级未净化 hop-by-hop 头(由 v1 P0-D 降级)
|
|
67
|
+
|
|
68
|
+
**位置**:`lib/index.js:468`(非缓冲直通 `res.writeHead(status, proxyRes.headers)`)、`:496-512`(WS upgrade 手拼响应头)。
|
|
69
|
+
|
|
70
|
+
**现状**:HTML 缓冲路径已删 `content-length/transfer-encoding`(`:459-461`),但非缓冲直通与 WS 101 分支把上游头(含 `connection/keep-alive/transfer-encoding` 等 hop-by-hop)原样透传。多数客户端能容忍,但严格客户端/中间层可能解析异常;WS 101 分支还允许上游注入任意响应头。
|
|
71
|
+
|
|
72
|
+
**复核结论**:成立,属健壮性/协议合规缺陷(非主动利用漏洞,风险中等)。**降为 P1**。
|
|
73
|
+
|
|
74
|
+
**对应**:R3。
|
|
75
|
+
|
|
76
|
+
### P1-2. QQ 流式失败回退在无 `stream_msg_id` 时可能重复发整条消息
|
|
77
|
+
|
|
78
|
+
**位置**:`lib/qq/node.js:296-314`。
|
|
79
|
+
|
|
80
|
+
**现状**:流式中途失败 → catch 用 `streamMsgId`(首片失败时为 null)+ `index: slices.length` + `inputState: 10` 补发完整 md;`streamMsgId` 为 null 时这是"全新流式消息",随后若再走 `_sendMarkdown` 兜底则重复。
|
|
81
|
+
|
|
82
|
+
**复核结论**:成立。首片失败场景 `streamMsgId=null`,补发其实开启了一条**新的** replace 流,随后 catch 内 `_sendMarkdown` 再发一条 → 用户可能收到 2 条完整内容。建议:`streamMsgId` 为空时直接 `_sendMarkdown`,不发 stream 补发。
|
|
83
|
+
|
|
84
|
+
**对应**:R4。
|
|
85
|
+
|
|
86
|
+
### P1-3. QQ 引用回复自动开新会话的固定 100ms 等待是竞态
|
|
87
|
+
|
|
88
|
+
**位置**:`lib/qq/node.js:410-417`。
|
|
89
|
+
|
|
90
|
+
**现状**:无活动会话且用户引用机器人消息时,先 `handleInbound('/new')` 再 `sleep(100ms)` 再处理正文。`/new` 创建会话是异步重活(等 DSH 建 agent),100ms 未必够 → 竞态下正文可能仍落在"无活动会话"路径。
|
|
91
|
+
|
|
92
|
+
**复核结论**:成立但影响较轻(最坏情况用户看到提示语而不是对话)。因 `handleInbound` 自带串行队列,直接 `await` `/new` 完成即可,去掉固定 sleep。
|
|
93
|
+
|
|
94
|
+
**对应**:R5。
|
|
95
|
+
|
|
96
|
+
### P1-4. `[SEND_FILE]` 可外发任意本地绝对路径文件
|
|
97
|
+
|
|
98
|
+
**位置**:`lib/platform/message-split.js:134-150` + `conversation-bridge.js` 自动发送。
|
|
99
|
+
|
|
100
|
+
**现状**:模型输出中 `[SEND_FILE: <绝对路径>]` 只要 statSync 是文件即发送给 IM。模型输出不可信(提示注入/越狱可能),可借此把 `.ssh`、`.credentials`、`.env` 等发给白名单聊天。
|
|
101
|
+
|
|
102
|
+
**复核结论**:成立。当前缓解仅"指令必须显式出现",但模型可被诱导输出。建议发送前校验目标在会话 cwd / 已注册工作区范围内,并拦截敏感路径(可复用 `security/path-validator.js`)。
|
|
103
|
+
|
|
104
|
+
**对应**:R6。
|
|
105
|
+
|
|
106
|
+
### P1-5. 微信 gateway `stop()`/`restart()` 轮询竞态
|
|
107
|
+
|
|
108
|
+
**位置**:`lib/wechat/gateway.js:389-418`、`769-791`。
|
|
109
|
+
|
|
110
|
+
**现状**:`stop()` 只置 `stopPollingLocal=true`,不中断 in-flight `getUpdates`(最长 35s);`restart()` `await previous` 后启动新循环,但旧任务真正退出前新循环可能已开始(软停止),且 `setCredentials()` 触发 restart 时若旧轮询未退出 → 新旧 poller 并存 → iLink 单 token 单 poller 可能 403。
|
|
111
|
+
|
|
112
|
+
**复核结论**:成立。`await previous` 确实等待旧循环退出(`previous` 是 `runPollLoop()` promise,`stopPollingLocal` 置位后旧循环在本次迭代结束时退出),所以**旧 poller 并存窗口有限**;但 35s 长轮询 in-flight 期间 restart 会等满整个长轮询才切换,用户体验与"快速换 token"场景确有竞态窗口。属真实健壮性缺陷(P1 偏低 / P2 偏高),建议给长轮询加 AbortController。
|
|
113
|
+
|
|
114
|
+
**对应**:R7。
|
|
115
|
+
|
|
116
|
+
### P1-6. 飞书群聊卡片按钮:**发起者本人会被误拦**(复核修正方向)
|
|
117
|
+
|
|
118
|
+
**位置**:`lib/feishu/node.js:176-194`(`_handleAction`)+ `_handleInbound`(`:141` `senderId: isGroup ? peerId : senderId`)+ 审批桥 `initiator = turn.senderId`(`:274`)。
|
|
119
|
+
|
|
120
|
+
**现状**:
|
|
121
|
+
- 群聊中发起审批:`pending.peerId` = 群 peerId(`oc_...`,因为 `turn.senderId` 在群聊被设成了群 peerId);
|
|
122
|
+
- 群成员点击卡片按钮:`operatorId` = 成员 open_id(`ou_...`);
|
|
123
|
+
- `_handleAction` 比对 `pending.peerId !== operatorId` → **恒不等 → 群聊按钮审批永远被拦**(`return` 无任何提示),群内只能走文本 `/yes`。
|
|
124
|
+
|
|
125
|
+
**复核结论**:成立且**方向与 v1 报告相反**——不是"他人可代批",而是"发起者本人按钮不可用"。单聊正常(operatorId=发起者 open_id)。若产品把"群"整体作为授权主体(首个 @ 即授权整群,成员都可发消息使唤 agent),则"群成员按钮可决议"本身不算越权;问题只是**群聊按钮路径被误杀**。
|
|
126
|
+
|
|
127
|
+
**对应**:R8(群聊按钮决议的 peer 口径:要么允许群内成员按钮决议,要么明确只允许发起成员、需记录成员级发起者并实测)。
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## 3. P2 代码质量 / 健壮性(复核后 11 项)
|
|
132
|
+
|
|
133
|
+
1. **cloudflared 启停状态机**(`cloudflared-manager`/`lib/index.js`):`startCloudflared` 非阻塞、90s 超时路径 `stop()` 后 reject、`.tmp` 残留(已有清理)。建议补状态机单测。
|
|
134
|
+
2. **tunnel-client SSE 截断**(`lib/tunnel-client.mjs:185`):收到第 2 个 chunk 即截断并 destroy 上游;500ms 定时器也截断。SSE 聊天流用户看到的是被腰斩的流。建议升级协议支持流式或文档明示"SSE 仅首屏"。
|
|
135
|
+
3. **微信 context token 明文落盘**(`wechat/gateway.js`):防抖合并写但 sync fs + 明文无权限收紧。建议文件权限 600 / 明确风险。
|
|
136
|
+
4. **`createConnectProxyAgent` 只支持 HTTP CONNECT**:`https://` 代理协议未特判,文档应说明。
|
|
137
|
+
5. **client 命令式 DOM 侵入 + 依赖宿主 class 名**(`client/index.js` 移动增强):对 DSH 版本升级脆弱,MutationObserver 常驻。建议收敛为官方 slot/注入点并加特性开关。
|
|
138
|
+
6. **`session-strip` 对 gzip 压缩响应不生效**:只在未压缩时剥离;压缩响应直接透传大字段。建议解压→剥离→重压缩,或上游请求 `accept-encoding: identity`。
|
|
139
|
+
7. **`checkVersion`/VersionBanner 无服务端 TTL 缓存**(`lib/index.js:931` + `client/index.js:2191`):**复核修正**:客户端 VersionBanner 仅在 mount 时 `check()` 一次(无 3s 轮询),因此"客户端 3s 轮询打 registry"**不成立**;剩余问题仅是服务端每次调用都发外网请求、无 TTL 缓存。降级为 P2 低优。
|
|
140
|
+
8. **`getDshVersion` 失败后不再重试**(`lib/index.js:598-643`,由 v1 P1-7 降级):函数入口即置 `_dshVersionLoaded=true`,失败后进程生命周期内不再探测(用户修复 PATH 需重启)。**v1 称"反复 spawn"是误读**,实际只探测一次;可议点仅为无重试。建议长退避(如 10 分钟后再探测)。
|
|
141
|
+
9. **`restartDsh` 直接 `process.exit`**:依赖外部守护,文档需明确使用前提。(保留)
|
|
142
|
+
10. **重复代码 / 命名不一致**:`getDshVersion` 与 `upgradePlugin` PATH 拼接重复;四处审批卡片渲染各写一遍(Telegram 有死代码 `sendApprovalCard`,见误报复审);平台 capability 键不一致(`supportsGroup` vs `group`)。建议统一。
|
|
143
|
+
11. **登录页/注入页面无 CSP**(P3 建议);大量空 `catch {}` 吞异常,建议至少 `logger.debug`。
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## 4. 架构层面观察(复核后 4 项,均成立)
|
|
148
|
+
|
|
149
|
+
1. **context 注入方式不一致**:`ctx.qq/telegram/feishu = ...` 直接属性赋值(部分 try/catch);测试用 `reflect.provide`。cordis 对未注入属性读取会抛错,建议统一 `reflect.provide`/服务注册。
|
|
150
|
+
2. **平台 capability 字段命名不一致**:`supportsGroup` vs `group`、`supportsMedia` vs `media`,消费方需各自适配;`status` getter 覆盖方式不一。建议统一 schema + JSDoc。
|
|
151
|
+
3. **错误处理粗糙**:大量空 catch;RPC 层统一 `fail('bad-request')` 无法区分参数错与内部错误。建议细分错误码。
|
|
152
|
+
4. **测试覆盖缺口**:`security-vulnerabilities.test.mjs` P0-7 是半断言(未真正调 verifyRequest/签发端点);P0-6 未单独覆盖伪造 `x-dsh-internal-tunnel` 需 secret 路径(verifyRequest 用 secret 比较,伪造不可行,测试隐式覆盖)。建议补 P0-A/P0-C/R6 的回归测试。
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## 5. 误报复审结论(v1 → v2 变更明细)
|
|
157
|
+
|
|
158
|
+
| 原编号 | 原结论 | 复审结论 | 处置 |
|
|
159
|
+
|---|---|---|---|
|
|
160
|
+
| P0-C | 隧道服务端鉴权"待补审" | 控制通道有 token 鉴权;但浏览器 HTTP/WS 转发路径**无自身认证**,仅靠本地 ProxyServer 认证兜底(纵深防御缺口) | 改写为 P0-C 实锤(定性随本地认证配置) |
|
|
161
|
+
| P1-5 | Telegram 按钮审批不校验发起者 | **误报**:Telegram 未重写审批桥,按钮分支是**死代码**(`sendApprovalCard` 无调用点),真实审批走基类文本卡 + `/yes` | 剔除,移入 P2 死代码清理建议 |
|
|
162
|
+
| P1-6 | 飞书群聊"他人可代批" | 方向反了:实际是**群聊发起者本人按钮被误拦**(peerId=群 vs operatorId=成员) | 改写为 P1-5(修正方向) |
|
|
163
|
+
| P1-7(v1) | getDshVersion 失败后反复 spawn | **误报**:函数入口已置 `_dshVersionLoaded=true`,只会尝试一次 | 降级为 P2 建议 |
|
|
164
|
+
| P1-8(v1) | VersionBanner 3s 轮询打 registry | **误报**:VersionBanner 仅 mount 时 check 一次,无轮询 | 剔除客户端轮询部分,保留服务端无 TTL 缓存(降 P2-7) |
|
|
165
|
+
| P2-8(v1) | upgradePlugin 分级提示 | 保留但影响轻微(`dsh` 失败转 npx/npm 是合理降级,误报"已是最新"概率低) | 降级合并至 P2-9 |
|
|
166
|
+
|
|
167
|
+
**复核中确认误报的共性根因**:v1 部分条目凭函数印象/推断而非逐行核对调用链,导致对"可达性"与"触发频率"高估。v2 已逐条回源码验证调用链与守卫条件。
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## 6. 修复清单与实施状态(按复核后优先级)
|
|
172
|
+
|
|
173
|
+
> 已按用户确认范围实施全部 P1 与低成本 P2;R1/R2 按用户选择「维持现状 + 仅文档告警」未改服务端逻辑,改为 UI 高危告警与 README 双语安全须知。
|
|
174
|
+
|
|
175
|
+
| 编号 | 文件 | 修复内容 | 优先级 | 状态 |
|
|
176
|
+
|---|---|---|---|---|
|
|
177
|
+
| R1 | `lib/auth/manager.js` | loopback 免认证收紧(未设凭据时不无条件放行本机进程) | P0 | **按用户决策未改**:UI 增加「已开启认证但未设任何密码」高危告警(client/index.js);README 双语安全须知 |
|
|
178
|
+
| R2 | `scripts/install-tunnel-server.sh` | 隧道入口加独立认证 | P0 | **按用户决策未改**:README 双语明确「隧道转发无独立认证、依赖本地访问认证」安全须知 |
|
|
179
|
+
| R3 | `lib/index.js` | 代理响应净化 hop-by-hop 头(直通 + WS 101 白名单 + 非 101 净化) | P1 | ✅ 已实施 |
|
|
180
|
+
| R4 | `lib/qq/node.js` | 流式失败回退:`streamMsgId` 为空时直接降级单条 Markdown | P1 | ✅ 已实施 |
|
|
181
|
+
| R5 | `lib/qq/node.js` | 引用回复去掉固定 100ms,await `/new` 完成 | P1 | ✅ 已实施 |
|
|
182
|
+
| R6 | `lib/platform/message-split.js` + `conversation-bridge.js` | SEND_FILE 发送前校验:必须位于会话 cwd 内 + 敏感路径黑名单(.ssh/.credentials/.env 等) | P1 | ✅ 已实施(`isPathAllowedForSend`) |
|
|
183
|
+
| R7 | `lib/wechat/gateway.js` | stop/restart 竞态:长轮询加 AbortController 中断 in-flight | P1 | ✅ 已实施 |
|
|
184
|
+
| R8 | `lib/feishu/node.js` | 群聊卡片按钮决议口径:群整体授权(群内成员可按钮决议),单聊仍校验发起者 | P1 | ✅ 已实施(按用户选择「群内成员可按钮决议」) |
|
|
185
|
+
| R9 | `lib/telegram/node.js` | 死代码清理:删除无调用点的 `sendApprovalCard` | P2 | ✅ 已实施 |
|
|
186
|
+
| R10 | `lib/index.js` | `getDshVersion` 失败长退避重试(10 分钟后再探测) | P2 | ✅ 已实施 |
|
|
187
|
+
| R11 | `lib/index.js` | `checkVersion` 服务端结果 TTL 缓存(10 分钟) | P2 | ✅ 已实施 |
|
|
188
|
+
|
|
189
|
+
### 实施验证结果
|
|
190
|
+
|
|
191
|
+
- ✅ 全量单测:**169 passed / 0 failed**(`npm test`)
|
|
192
|
+
- ✅ lint:**0 errors**(28 条既存 warning,均为历史代码 no-unused-vars 降级,非本次引入)
|
|
193
|
+
- ✅ 构建:`npm run build:client` 成功产出 `client/client.js`
|
|
194
|
+
- ✅ 语法检查:全部改动文件 `node --check` 通过
|
|
195
|
+
|
|
196
|
+
### 仍需真机验证(不在本次单测覆盖内)
|
|
197
|
+
|
|
198
|
+
- 飞书群聊卡片按钮:修复后群内任意成员可按钮决议(与 QQ 群模型一致),需真机验证按钮事件链路;
|
|
199
|
+
- 微信 stop/restart 竞态:AbortController 中断长轮询的行为需在真实 iLink 轮询下确认;
|
|
200
|
+
- QQ 流式失败回退 / 引用回复:需真实 QQ Bot 环境验证。
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## 8. Issue #28 修复记录(DSH 原生端口 3080 直连被误判远程)
|
|
206
|
+
|
|
207
|
+
**issue**:[#28](https://github.com/wenbin-wb/dsh-bridge/issues/28) — dsh 0.1.2-alpha.5 下 127.0.0.1:3080 被判定为远程 + 本机文件夹选择走远程抽屉(0 回复,经代码核查确认为真问题)。
|
|
208
|
+
|
|
209
|
+
**根因(现象 1,已修复)**:
|
|
210
|
+
- `/__dsh_bridge__/loopback-token`(本机领 adminToken 端点)只注册在代理端口 3082(lib/index.js ProxyServer),DSH 原生端口 3080 无此端点;
|
|
211
|
+
- 3080 页面相对路径 fetch 404 后,兜底跨域请求 3082 时,Origin `http://127.0.0.1:3080` 不在 CORS 白名单 → 被浏览器拦截 → 拿不到 adminToken;
|
|
212
|
+
- `adminPolicy=local_only` 下 `checkAdminAuth` 要求有效 adminToken → 面板锁定。
|
|
213
|
+
|
|
214
|
+
**修复**:`BridgeService.startProxy()` 的 `allowedOrigins` 加入 DSH 原生端口 origin(`http://127.0.0.1:<dshPort>` / `http://localhost:<dshPort>`),使直连原生端口时页面可跨域回读代理端点的 loopback-token。CORS 仍收敛(仅回显白名单 origin,非白名单不回显 ACAO 头,浏览器跨域仍被拦)。
|
|
215
|
+
|
|
216
|
+
**回归测试**:`test/proxy-auth.test.mjs` 新增「issue #28 regression」用例——白名单 dshPort origin 回环 POST 得 200 + ACAO 回显 + 有效 adminToken;非白名单 origin 不回显 ACAO。
|
|
217
|
+
|
|
218
|
+
**现象 2(文件夹选择走远程抽屉,未修)**:纯 `dsh web` 模式无原生 pickDirectory(无 Electron/原生宿主桥),`pick()` 抛 "needs the native capability" 后刻意降级远程抽屉。是否可修取决于 DSH 0.1.2 是否提供替代目录选择服务,需真机/DSH 侧确认。
|
|
219
|
+
|
|
220
|
+
> 本报告为 CODE_REVIEW 文档,不随 npm 包发布。修复均未 bump 版本号;如发布请自行决定版本与 CHANGELOG。
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# Cloudflare 固定域名(Token 模式)申请与配置教程
|
|
2
|
+
|
|
3
|
+
> 适用:想让 dsh-bridge 的公网入口**固定不变**(如 `dsh.yourdomain.com`),而不是每次开启都换新的
|
|
4
|
+
> `trycloudflare.com` 随机地址。全程免费(Cloudflare 免费套餐即可),无需公网 IP、无需备案(境外流量)。
|
|
5
|
+
|
|
6
|
+
- 预计耗时:20–40 分钟(含域名生效等待)
|
|
7
|
+
- 费用:Cloudflare 免费套餐 $0;域名需自购(约 ¥30–80/年,.com/.net/.xyz 等均可)
|
|
8
|
+
- 💡 **界面语言**:Cloudflare 控制台支持中文。切换方式:页面右上角头像/语言菜单选择「中文(简体)」。本教程同时给出**中英文菜单对照**(如「添加站点 / Add a site」——斜杠前是中文界面名称,斜杠后是英文原名),用哪个界面都能对上。
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## 原理一句话
|
|
13
|
+
|
|
14
|
+
你在 Cloudflare 建一条 **Named Tunnel(固定隧道)**,把 `dsh.yourdomain.com` 这个子域名的流量经 Cloudflare 边缘转发到
|
|
15
|
+
你电脑上 dsh-bridge 的本地端口(默认 `3082`)。dsh-bridge 只需要拿到这条隧道的 **Token** 就能替你后台运行
|
|
16
|
+
`cloudflared tunnel run --token <TOKEN>`,隧道进程由 dsh-bridge 托管(崩溃自动重启、随 DSH 启动自愈),域名永不改变。
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
手机/浏览器 → https://dsh.yourdomain.com → Cloudflare 边缘 → 加密隧道 → 你的电脑:3082 → DSH
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## 第 1 步:注册 Cloudflare 账号
|
|
25
|
+
|
|
26
|
+
1. 打开 <https://dash.cloudflare.com/sign-up>,用邮箱注册(或 Google 账号登录)。
|
|
27
|
+
2. 登录后进入 Dashboard。
|
|
28
|
+
|
|
29
|
+
## 第 2 步:准备一个域名
|
|
30
|
+
|
|
31
|
+
两种方式任选:
|
|
32
|
+
|
|
33
|
+
- **已有域名**:确保域名的 DNS 托管在 Cloudflare(见第 3 步)。
|
|
34
|
+
- **没有域名**:
|
|
35
|
+
1. 在任意注册商购买一个(.com / .net / .xyz / .top 均可,便宜的先练手也行);
|
|
36
|
+
2. 拿到域名的 **NS 记录**(或注册商 API 权限),准备迁到 Cloudflare。
|
|
37
|
+
|
|
38
|
+
## 第 3 步:把域名接入 Cloudflare(DNS 托管)
|
|
39
|
+
|
|
40
|
+
> 固定域名隧道要求域名由 Cloudflare 托管(DNS 生效才能签发证书、路由流量)。若域名已在 Cloudflare 可跳过本步。
|
|
41
|
+
|
|
42
|
+
1. Cloudflare Dashboard(控制台)→「**添加站点 / Add a site**」→ 输入你的域名 → 选 **Free(免费)** 套餐 → Continue(继续)。
|
|
43
|
+
2. Cloudflare 会扫描现有 DNS 记录并导入(自动保留)。
|
|
44
|
+
3. 按提示到**域名注册商**处,把域名的 NS(Name Server/名称服务器)改成 Cloudflare 给的两条(形如 `xxx.ns.cloudflare.com`)。
|
|
45
|
+
4. 回 Cloudflare 点「**检查名称服务器 / Check nameservers**」,等待生效(通常几分钟到 24 小时,多数 1 小时内)。
|
|
46
|
+
5. 状态变 **Active(有效)** 即托管完成。
|
|
47
|
+
|
|
48
|
+
## 第 4 步:进入 Zero Trust 创建固定隧道
|
|
49
|
+
|
|
50
|
+
1. 打开 Zero Trust 控制台:<https://one.dash.cloudflare.com/>
|
|
51
|
+
(或 Cloudflare Dashboard 左侧 → **Zero Trust**)。
|
|
52
|
+
2. 首次使用会让你选团队名、套餐——选 **Free(免费)** 计划即可。
|
|
53
|
+
3. 左侧菜单:**Networks(网络)→ Tunnels(隧道)**。
|
|
54
|
+
4. 点 **Create a tunnel(创建隧道)**:
|
|
55
|
+
- 连接器类型选 **Cloudflared**;
|
|
56
|
+
- Tunnel name(隧道名称):起个名,如 `dsh-home`;
|
|
57
|
+
- 点 **Save tunnel(保存隧道)**。
|
|
58
|
+
5. **⚠️ 立即复制并保存 Token(这一步最要紧!)**
|
|
59
|
+
保存后页面会给出**安装并运行连接器**的命令,里面带一长串 Token:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
cloudflared tunnel run --token eyJhIjoi...(极长的一串)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**请现在就把 `--token` 后面那一长串完整复制出来**,粘贴到记事本/密码管理器存好(可以顺手存好 `dsh.yourdomain.com` 这个域名一起备忘)。**复制完再点任何下一步/关闭页面**——万一丢了,见本步下方"Token 找不回来了怎么办"。
|
|
66
|
+
|
|
67
|
+
> 🤖 **这段命令不用你自己在电脑上运行**(无论 Windows / macOS / Linux 都不需要)——dsh-bridge 会在后台替你执行
|
|
68
|
+
> `cloudflared tunnel run --token ...`。这里展示命令只是为了让你看清 Token 长在哪、方便复制那一长串。
|
|
69
|
+
|
|
70
|
+
> 💡 **Token 找不回来了怎么办**(不用重建隧道,随时可取):
|
|
71
|
+
>
|
|
72
|
+
> 回到 **Networks(网络)→ Tunnels(隧道)** 列表 → 点你的**隧道名称**进入详情页 → 找 **Configure(配置)**按钮或右上角 **⋯** 菜单(不同时期界面略有差异),点击后页面会显示该隧道的 **Token**,或重新给出 `cloudflared tunnel run --token ...` 的安装命令——复制其中那一长串即可。
|
|
73
|
+
>
|
|
74
|
+
> 取 token 时**无需在电脑上运行任何命令**,Token 就在网页里,直接复制即可。
|
|
75
|
+
|
|
76
|
+
## 第 5 步:给隧道绑定你的固定子域名(Public Hostname / 公共主机名)
|
|
77
|
+
|
|
78
|
+
> ⚠️ **关键**:隧道本身不含"域名→本地端口"的路由规则,必须在这里配。而且这个子域名要和你之后在
|
|
79
|
+
> dsh-bridge 面板里填的「自定义固定域名」**完全一致**。
|
|
80
|
+
|
|
81
|
+
1. 隧道创建后进入隧道详情页 → 切到 **Public Hostname(公共主机名)** 标签 → **Add a public hostname(添加公共主机名)**。
|
|
82
|
+
2. 填写(中英文界面字段对照):
|
|
83
|
+
- **Subdomain(子域)**:如 `dsh`
|
|
84
|
+
- **Domain(域)**:下拉选你的域名(如 `yourdomain.com`)
|
|
85
|
+
- 即最终 = `dsh.yourdomain.com`
|
|
86
|
+
- **Service(服务)→ Type(类型)**:`HTTP`
|
|
87
|
+
- **URL**:`localhost:3082`(dsh-bridge 代理端口;如果你改过 dsh-bridge 端口则填对应端口)
|
|
88
|
+
3. 保存(**Save / 保存**)。Cloudflare 会自动为你签发该域名的免费 TLS 证书并创建 DNS 记录(Tunnel 类型,CNAME 指向 `*.cfargotunnel.com`),无需手动配 DNS。
|
|
89
|
+
4. 状态稍后变为 **Healthy(运行正常)**(见隧道详情的 Connectors(连接器)与 Public Hostname(公共主机名)状态)。
|
|
90
|
+
|
|
91
|
+
> 端口说明:dsh-bridge 默认把面板代理在 `3082`(`proxyPort`),DSH 原生在 `3080`。**必须指向 3082**(走 dsh-bridge 的
|
|
92
|
+
> 认证/会话/二维码/隧道控制逻辑),不要直接指 3080。
|
|
93
|
+
|
|
94
|
+
> 端口说明:dsh-bridge 默认把面板代理在 `3082`(`proxyPort`),DSH 原生在 `3080`。**必须指向 3082**(走 dsh-bridge 的
|
|
95
|
+
> 认证/会话/二维码/隧道控制逻辑),不要直接指 3080。
|
|
96
|
+
|
|
97
|
+
## 第 6 步:把 Token 和域名填回 dsh-bridge
|
|
98
|
+
|
|
99
|
+
1. 打开 dsh-bridge 面板 → **公网隧道** tab。
|
|
100
|
+
2. 展开底部「**⚙️ 隧道配置**」→「**Cloudflare 隧道**」卡 → 展开「**高级配置:固定域名 (Cloudflare Token)**」。
|
|
101
|
+
3. 填写两项并「保存固定域名配置」:
|
|
102
|
+
- **自定义固定域名**:`dsh.yourdomain.com`(与第 5 步 Public Hostname **完全一致**,不要带 `https://`)
|
|
103
|
+
- **Tunnel Token**:第 4/5 步复制的 `cloudflared tunnel run --token` 后的那一长串
|
|
104
|
+
4. 回到顶部「**公网访问入口**」卡 → 点「**开启**」(token 模式下该卡显示固定域名模式)。
|
|
105
|
+
5. 几秒后状态变 **固定隧道已建立 (https://dsh.yourdomain.com)**。
|
|
106
|
+
|
|
107
|
+
## 第 7 步:验证
|
|
108
|
+
|
|
109
|
+
1. 浏览器打开 `https://dsh.yourdomain.com` → 应出现 DSH 登录页/面板(取决于你的访问认证设置)。
|
|
110
|
+
2. Cloudflare Zero Trust → **Networks(网络)→ Tunnels(隧道)** → 该隧道 → **Healthy(运行正常)**,Connectors(连接器)有连接。
|
|
111
|
+
3. 重启 DSH 服务或重启电脑,勾选「随 DSH 启动自动开启」后域名保持不变、自动恢复。
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## 常见问题
|
|
116
|
+
|
|
117
|
+
### Q1:面板显示"已建立"但浏览器打不开?
|
|
118
|
+
几乎都是 **Public Hostname 与面板填的域名不一致**,或 Service URL 端口写错(应为 `localhost:3082` 而非 3080)。
|
|
119
|
+
逐项核对第 5、6 步。
|
|
120
|
+
|
|
121
|
+
### Q2:提示"cloudflared 启动失败: Incorrect Usage / flag provided but not defined"?
|
|
122
|
+
请确认 dsh-bridge ≥ **v2.10.7**(v2.10.6 曾因 `--no-autoupdate` 参数位置导致固定域名隧道秒退,已修复)。
|
|
123
|
+
|
|
124
|
+
### Q3:隧道一直"自动重连中"然后 error?
|
|
125
|
+
先看面板 error 文案:
|
|
126
|
+
- 含 **Incorrect Usage / 配置错误** → Token 复制不完整或参数问题,重新复制整串 Token;
|
|
127
|
+
- 含 **退出 code≠0 / 连接失败** → 多为网络到 Cloudflare 边缘不通或 Token 无效,检查网络后点「开启」重试(自愈已内置退避重试)。
|
|
128
|
+
|
|
129
|
+
### Q4:需要买最贵的套餐吗?
|
|
130
|
+
不用。Cloudflare **Free** 套餐即可创建固定隧道、绑定子域名、免费 TLS。仅当你想在同一域名下加很多复杂规则或团队审计时才需付费。
|
|
131
|
+
|
|
132
|
+
### Q5:域名必须在 Cloudflare 托管吗?
|
|
133
|
+
**固定域名隧道是的**(要在 Cloudflare DNS 建 `CNAME → *.cfargotunnel.com` 的记录)。不想迁移主域名的话,
|
|
134
|
+
可以买一个便宜小域名专门做隧道入口,把主域名留在原注册商。
|
|
135
|
+
|
|
136
|
+
### Q6:为什么面板里"复制 Token"和 Cloudflare 命令里看到的不一样长?
|
|
137
|
+
Token 就是 `cloudflared tunnel run --token` 后面那一长串(不含引号、不含 `--token` 字样本身)。
|
|
138
|
+
若你在别处看到的 token 是用于 `cloudflared tunnel login` 的,那是不同的东西——固定域名模式要的是 **run --token** 那个。
|
|
139
|
+
|
|
140
|
+
### Q7:创建隧道时忘了复制 Token,页面也关了,去哪找?
|
|
141
|
+
**不需要重建隧道**,Token 随时能在网页里取回:
|
|
142
|
+
|
|
143
|
+
1. 打开 Zero Trust 控制台 → 左侧 **Networks(网络)→ Tunnels(隧道)**;
|
|
144
|
+
2. 在隧道列表里**点击你那条隧道的名称**(不是行尾的按钮,是名称本身)进入详情;
|
|
145
|
+
3. 详情页找 **Configure(配置)** 按钮,或页面右上角的 **⋯ / 更多** 菜单(不同时期的界面位置可能略有差异,但一定在隧道详情页里);
|
|
146
|
+
4. 点击后页面会显示该隧道的 **Token**,或者重新给出带 Token 的安装命令(`cloudflared tunnel run --token ...`);
|
|
147
|
+
5. 复制命令中 `--token` 后面那一长串即可。
|
|
148
|
+
|
|
149
|
+
> 提示:如果你在列表页只看到 **⋯** 下拉里有 **Edit / 编辑 / Delete / 删除** 而没有 Token,就点**隧道名称先进详情**,
|
|
150
|
+
> Token 在详情页内。整个过程都在浏览器网页里完成,**不需要在电脑上装 cloudflared 或执行任何命令**。
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## 相关链接
|
|
155
|
+
|
|
156
|
+
- [Cloudflare Zero Trust 控制台](https://one.dash.cloudflare.com/)
|
|
157
|
+
- [Cloudflare 官方:Create a tunnel (dashboard)](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/get-started/create-remote-tunnel/)
|
|
158
|
+
- [Cloudflare 官方:Tunnel run 参数(--token / --no-autoupdate)](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/configure-tunnels/run-parameters/)
|
package/docs/telegram-usage.md
CHANGED
|
@@ -90,4 +90,4 @@
|
|
|
90
90
|
2. **安全白名单拦截**:
|
|
91
91
|
- 仅白名单内的用户消息会被放行给 Agent;非白名单用户发送的消息会被直接忽略,绝不消耗 Token 或喂给模型。
|
|
92
92
|
3. **敏感操作审批**:
|
|
93
|
-
- 当 Agent 尝试执行系统命令或敏感文件读写时,Telegram 会自动下发 `[✓ 批准执行]` / `[✕ 拒绝执行]` 交互按键,10 分钟未处理自动超时拒绝。
|
|
93
|
+
- 当 Agent 尝试执行系统命令或敏感文件读写时,Telegram 会自动下发 `[✓ 批准执行]` / `[✕ 拒绝执行]` 交互按键,10 分钟未处理自动超时拒绝。
|