dsh-remote-bridge 2.5.2 → 2.6.1
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/README.en.md +53 -11
- package/README.md +95 -12
- package/docs/api/asyncapi.yaml +1 -1
- package/docs/api/openapi.yaml +190 -4
- package/kotlin-sdk/src/main/kotlin/ai/deepseek/dsh/workspace/DshWorkspaceClient.kt +6 -0
- package/lib/client.cjs +160 -83
- package/lib/index.d.ts +261 -5
- package/lib/index.js +1184 -92
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
11
|
<p align="center">
|
|
12
|
-
<img alt="Version" src="https://img.shields.io/badge/version-v2.
|
|
12
|
+
<img alt="Version" src="https://img.shields.io/badge/version-v2.6.1-087f8c">
|
|
13
13
|
<img alt="DSH plugin" src="https://img.shields.io/badge/DeepSeek_Harness-plugin-1f2328">
|
|
14
14
|
<img alt="Platforms" src="https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux-586069">
|
|
15
15
|
<img alt="License" src="https://img.shields.io/badge/license-AGPL--3.0-2da44e">
|
|
@@ -102,10 +102,10 @@ dsh plugin --profile web add dsh-remote-bridge
|
|
|
102
102
|
dsh plugin --profile web add dsh-remote-bridge --registry https://registry.npmmirror.com
|
|
103
103
|
|
|
104
104
|
# 2. tarball from the GitHub release
|
|
105
|
-
dsh plugin --profile web add https://github.com/westanke/dsh-remote-bridge/releases/download/v2.
|
|
105
|
+
dsh plugin --profile web add https://github.com/westanke/dsh-remote-bridge/releases/download/v2.6.1/dsh-remote-bridge-v2.6.1.tgz
|
|
106
106
|
|
|
107
107
|
# 3. tarball from the Gitee release (faster from mainland China)
|
|
108
|
-
dsh plugin --profile web add https://gitee.com/westanke/dsh-remote-bridge/releases/download/v2.
|
|
108
|
+
dsh plugin --profile web add https://gitee.com/westanke/dsh-remote-bridge/releases/download/v2.6.1/dsh-remote-bridge-v2.6.1.tgz
|
|
109
109
|
|
|
110
110
|
npx @deepseek-ai/dsh web
|
|
111
111
|
```
|
|
@@ -122,7 +122,7 @@ Restart DSH after installation, then open settings — The section is titled **
|
|
|
122
122
|
To install a downloaded release archive:
|
|
123
123
|
|
|
124
124
|
```sh
|
|
125
|
-
dsh plugin --profile web add ./dsh-remote-bridge-v2.
|
|
125
|
+
dsh plugin --profile web add ./dsh-remote-bridge-v2.6.1.tgz
|
|
126
126
|
```
|
|
127
127
|
|
|
128
128
|
## First connection
|
|
@@ -219,10 +219,28 @@ The following controls remain active over both HTTP and HTTPS:
|
|
|
219
219
|
- revocable scopes and per-root grants;
|
|
220
220
|
- separate rate limits for pairing, authentication failures, and normal requests;
|
|
221
221
|
- audit records that omit tokens, file bodies, and chat bodies;
|
|
222
|
-
- no automatic access to roots added after a device was paired
|
|
222
|
+
- no automatic access to roots added after a device was paired;
|
|
223
|
+
- provider `baseURL` changes are limited to public http(s) destinations, and moving a
|
|
224
|
+
provider that already stores a credential to a new host also requires a fresh API key
|
|
225
|
+
(the kernel sends that key to the address, so this is a credential boundary, not just SSRF).
|
|
223
226
|
|
|
224
227
|
Only a loopback client may add roots, change device grants, create pairing codes, configure the listener, or permanently purge trash.
|
|
225
228
|
|
|
229
|
+
### Local LLM endpoints (ollama / LM Studio)
|
|
230
|
+
|
|
231
|
+
Private, loopback, link-local and CGNAT provider addresses are **rejected by default**.
|
|
232
|
+
To use a local model server, list it explicitly in the plugin config:
|
|
233
|
+
|
|
234
|
+
```yaml
|
|
235
|
+
allowPrivateProviderHosts:
|
|
236
|
+
- 127.0.0.1:11434 # recommended: pin the port
|
|
237
|
+
- 192.168.1.5 # bare host: allows every port on that host
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Exact `host` or `host:port` only — CIDR is not supported. Prefer the `host:port` form:
|
|
241
|
+
allowing `192.168.1.5` allows every service on it, while ollama only listens on 11434.
|
|
242
|
+
See `docs/project/SECURITY.md` for the reasoning and the audit trail fields.
|
|
243
|
+
|
|
226
244
|
## Path boundary
|
|
227
245
|
|
|
228
246
|
External requests use `rootId + relativePath`, never an absolute server path.
|
|
@@ -297,6 +315,16 @@ A device also needs an explicit grant for every `rootId`. Local administrators m
|
|
|
297
315
|
|
|
298
316
|
**New scopes are picked before pairing, in Remote access**: whatever you tick is what the generated pairing code carries, and those scopes take effect once the app enters the code. For a device that is already paired, open **Devices** and use its **Permissions** button.
|
|
299
317
|
|
|
318
|
+
The default selection is five scopes — `chat.read`, `chat.write`, `files.read`, `files.write`, `settings.read` — and **deliberately excludes** `files.delete` and `settings.write`. You can still tick them; the default just no longer hands out destructive permissions on its own.
|
|
319
|
+
|
|
320
|
+
### Device names and models
|
|
321
|
+
|
|
322
|
+
At pairing time the bridge has no idea who will use the token, so a new device starts with a **platform-neutral** placeholder name (`Mobile device`). The first time the device connects, the bridge works out its platform and model from the request and replaces the name with `platform · model` (for example `HarmonyOS · ICL-AL10`).
|
|
323
|
+
|
|
324
|
+
Platform detection prefers the client-declared `X-Dsh-Device: <platform>/<model>` header and falls back to the `User-Agent`. **The model is only taken from what the client reports**: neither okhttp nor ArkWeb puts a model in its default UA, and the bridge will not guess — a wrong model is worse than no model.
|
|
325
|
+
|
|
326
|
+
Open **Devices** and use **Permissions** to rename a device. **A name you set yourself is never overwritten**; an automatically generated name still updates when the device reports a new identity. Device names are for display and troubleshooting only and take no part in authorization.
|
|
327
|
+
|
|
300
328
|
**Revoking a device deletes it from the database** rather than leaving a marker row behind. Its grants go with it, while the audit trail (who revoked which device, and when) is kept.
|
|
301
329
|
|
|
302
330
|
## Upgrade and uninstall
|
|
@@ -354,19 +382,33 @@ pnpm pack
|
|
|
354
382
|
|
|
355
383
|
`pnpm check` covers type-checking, documentation consistency, and tests. It currently passes **67 test files / 557 cases**, plus **8 browser-layer cases** (Playwright measuring real computed styles, including the 375px narrow-viewport contract).
|
|
356
384
|
|
|
357
|
-
`npm pack` produces roughly a **0.
|
|
385
|
+
`npm pack` produces roughly a **0.64 MB** archive (v2.6.1 measured 669,434 bytes, SHA-256 `9bf0189b…2cd35`): 2.2.0 dropped the standalone page and its editor dependencies and turned source maps off, down from 1.72 MB; it has since grown slightly with projection passthrough, the permission BFF, and the five-dimension hardening.
|
|
358
386
|
|
|
359
387
|
v2.5.0 was verified on DSH **0.2.1-rc.2** on real hardware: all 14/14 host services injected, the three jobs endpoints exercised, and the permission catalog returned the kernel's real three presets. The development baseline is upstream `dsh-v0.2.1-rc.2` (commit `687f282e8625c6b8d11ff58211a90524be2e4317`); see `engines.dsh` above for the supported range. CI covers Windows, Ubuntu, and macOS.
|
|
360
388
|
|
|
361
|
-
**v2.
|
|
389
|
+
**v2.6.1 verification status**: 74 test files / 682 cases green, plus 8 Playwright browser cases
|
|
362
390
|
(including the 375px narrow-viewport contract); `typecheck`, `docs:check`, and the build all pass.
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
391
|
+
The SEC-H11 attack chain was confirmed **by measurement** (a fake LLM endpoint was stood up and the
|
|
392
|
+
kernel was observed sending the real `Authorization: Bearer` to it); each of the four gates now has a
|
|
393
|
+
regression assertion. The three device-identity specs (27 + 13 + 12 cases) cover the UA detection
|
|
394
|
+
order, "a `declared` name is never overwritten", and idempotent migration of the old table shape.
|
|
366
395
|
|
|
367
396
|
## Project
|
|
368
397
|
|
|
369
|
-
- Version: `v2.
|
|
398
|
+
- Version: `v2.6.1`
|
|
399
|
+
- **v2.6.1 is the security-fix + device-identity release**: it closes a **high-severity** hole —
|
|
400
|
+
the three provider `baseURL` entry points (create / update / model discovery) performed no
|
|
401
|
+
validation at all, so a device holding `settings.write` could point the baseURL at its own server
|
|
402
|
+
and the kernel would request `/models` there with **that provider's real API key**, leaking the
|
|
403
|
+
credential outright; the persistent variant was worse, since after changing the baseURL every
|
|
404
|
+
subsequent chat would send the key there. There are now three gates (protocol allow-list,
|
|
405
|
+
private networks denied by default with an explicit `allowPrivateProviderHosts` opt-in, and a
|
|
406
|
+
new key required when repointing a provider that already holds a credential), and the pairing
|
|
407
|
+
default was narrowed from seven scopes to five (no more `settings.write` / `files.delete` by default).
|
|
408
|
+
It also fixes "the device name is always 'Android device'": the bridge now derives the platform and
|
|
409
|
+
model from the `X-Dsh-Device` header (User-Agent as fallback), renames the placeholder to
|
|
410
|
+
"platform · model", and offers a rename channel (a manually set name is never overwritten).
|
|
411
|
+
- **v2.6.0 is the plugin-settings + IM-entry release**: it adds the per-plugin settings namespaces (`GET /api/v1/settings/namespaces`, `PATCH .../{ns}`) so the app can build the form from the kernel schema, and two entry points for "I am out and the desktop is still locked" — the host command `/tunnel` and the tool `open_tunnel_config`; plus one-tap pruning of unused devices, the audit page removed, and a guard so a failing optional registration cannot tear down the whole plugin context.
|
|
370
412
|
- **v2.5.2 is a comprehensive hardening release**: it lands the five-dimension audit
|
|
371
413
|
(security, features, performance, UI, button feedback) — including the unauthenticated
|
|
372
414
|
`debug/host-services` information disclosure fix, dual-bucket rate limiting, broadcast backpressure,
|
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
11
|
<p align="center">
|
|
12
|
-
<img alt="Version" src="https://img.shields.io/badge/version-v2.
|
|
12
|
+
<img alt="Version" src="https://img.shields.io/badge/version-v2.6.1-087f8c">
|
|
13
13
|
<img alt="DSH plugin" src="https://img.shields.io/badge/DeepSeek_Harness-plugin-1f2328">
|
|
14
14
|
<img alt="Platforms" src="https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux-586069">
|
|
15
15
|
<img alt="License" src="https://img.shields.io/badge/license-AGPL--3.0-2da44e">
|
|
@@ -93,10 +93,10 @@ dsh plugin --profile web add dsh-remote-bridge
|
|
|
93
93
|
dsh plugin --profile web add dsh-remote-bridge --registry https://registry.npmmirror.com
|
|
94
94
|
|
|
95
95
|
# ② GitHub Release 的 tarball
|
|
96
|
-
dsh plugin --profile web add https://github.com/westanke/dsh-remote-bridge/releases/download/v2.
|
|
96
|
+
dsh plugin --profile web add https://github.com/westanke/dsh-remote-bridge/releases/download/v2.6.1/dsh-remote-bridge-v2.6.1.tgz
|
|
97
97
|
|
|
98
98
|
# ③ Gitee Release 的 tarball(国内访问更快)
|
|
99
|
-
dsh plugin --profile web add https://gitee.com/westanke/dsh-remote-bridge/releases/download/v2.
|
|
99
|
+
dsh plugin --profile web add https://gitee.com/westanke/dsh-remote-bridge/releases/download/v2.6.1/dsh-remote-bridge-v2.6.1.tgz
|
|
100
100
|
|
|
101
101
|
npx @deepseek-ai/dsh web
|
|
102
102
|
```
|
|
@@ -112,7 +112,7 @@ npx @deepseek-ai/dsh web
|
|
|
112
112
|
从 GitHub Release 下载离线安装包时,可直接安装 tarball:
|
|
113
113
|
|
|
114
114
|
```sh
|
|
115
|
-
dsh plugin --profile web add ./dsh-remote-bridge-v2.
|
|
115
|
+
dsh plugin --profile web add ./dsh-remote-bridge-v2.6.1.tgz
|
|
116
116
|
```
|
|
117
117
|
|
|
118
118
|
## 第一次使用
|
|
@@ -182,14 +182,59 @@ dsh plugin --profile web add ./dsh-remote-bridge-v2.5.2.tgz
|
|
|
182
182
|
装好 App 后,在「手机设置 → 远程访问」里生成配置文本或配对码即可连上本机。
|
|
183
183
|
公开 API 不依赖 DSH 私有协议,也可用于其他客户端:
|
|
184
184
|
|
|
185
|
-
- 查看、新建、重命名和移除 DSH
|
|
186
|
-
- 创建、重命名、分叉和归档会话。
|
|
185
|
+
- 查看、新建、重命名和移除 DSH 工作区登记。- 创建、重命名、分叉和归档会话。
|
|
187
186
|
- 读取消息、流式接收正文和思考、追加引导或取消运行。
|
|
188
187
|
- 查看 TODO、斜杠命令、权限模式和待审批操作。
|
|
189
188
|
- 选择 Agent、模型和思考强度。
|
|
190
189
|
- 查看或修改模型供应商,支持完全自定义供应商。
|
|
191
190
|
- 管理设备获准访问的文件和回收站项目。
|
|
192
191
|
|
|
192
|
+
### 远程开隧道:人已经出门、电脑还锁着
|
|
193
|
+
|
|
194
|
+
常见场景:**人已经出门,才发现电脑上的 Cloudflare 隧道没开,配置文本也没发给自己**。
|
|
195
|
+
此时公网入口根本不存在,WebUI 和 App 都够不着这台机器。能用的只剩「本机主动连出去」的通道,
|
|
196
|
+
装了 `@xmanrui/dsh-im`(微信 / 飞书 / 钉钉 / 企业微信 / Telegram …)或任何带命令栏的客户端时,
|
|
197
|
+
有两条入口可以做这件事:
|
|
198
|
+
|
|
199
|
+
**① 桌面 WebUI 的命令栏(立即可用)**
|
|
200
|
+
|
|
201
|
+
```
|
|
202
|
+
/tunnel
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
回的是这样一段:
|
|
206
|
+
|
|
207
|
+
```
|
|
208
|
+
云隧道已开启。
|
|
209
|
+
地址:https://<random>.trycloudflare.com
|
|
210
|
+
设备名:Android 设备
|
|
211
|
+
配置文本(一行,复制后粘贴到 App 的「导入配置」):
|
|
212
|
+
DSH1:eyJ2IjoxLCJlbmRwb2ludHMiOlt7ImxhYmVsIjoi…
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
**② IM 聊天窗口里用一句话(配合智能体)**
|
|
216
|
+
|
|
217
|
+
在微信/飞书里直接说「**帮我开隧道,把配置文本发我**」,智能体会调用本插件的
|
|
218
|
+
`open_tunnel_config` 工具,把同样的地址与 `DSH1:` 文本回到聊天里。
|
|
219
|
+
|
|
220
|
+
> 为什么 IM 里不是发 `/tunnel`?因为 `@xmanrui/dsh-im` 的斜杠命令表是**封闭的**:
|
|
221
|
+
> 内置 31 条,另外只把 `/compact` `/clear` `/permission` 三条委派给宿主,没有
|
|
222
|
+
> 「未知命令转交宿主命令表」的通用派发,也从不查询宿主的命令列表。所以 `/tunnel`
|
|
223
|
+
> 在 IM 里会当成一句普通消息交给智能体 —— 这也正是上面第 ② 条走自然语言 + 工具的原因。
|
|
224
|
+
> 如果 dsh-im 以后加上通用派发,`/tunnel` 会立刻可用,桥这边不用改。
|
|
225
|
+
|
|
226
|
+
隧道**已经在跑**时再触发一次不会重复开,而是复用当前地址再给一份配置文本
|
|
227
|
+
(每次调用都会新建一台设备,方便在面板里单独吊销)。
|
|
228
|
+
|
|
229
|
+
能力边界刻意压得很小:**只能开,不能停、不能改配置、不读任何文件**。
|
|
230
|
+
带参数一律拒绝,不提供 `/tunnel stop` 这类第二入口。就算口令泄露,损失上限也只是
|
|
231
|
+
「隧道被人开了一下」—— 隧道本身不授予任何数据面权限(读文件、发消息仍要设备令牌)。
|
|
232
|
+
|
|
233
|
+
> ⚠️ **必要前提:IM 机器人必须是白名单模式,且白名单里只有你自己。**
|
|
234
|
+
> 这条通道的身份校验**不在本插件里**,而在 IM 插件自己的访问策略(`canExecuteCommands` +
|
|
235
|
+
> allowlist)。如果机器人设成「允许所有用户私聊执行命令」,那等于把开机按钮放到公共群里 ——
|
|
236
|
+
> 这是部署问题,代码替你兜不住。详见 [SECURITY.md](docs/project/SECURITY.md#im-命令tunnel的信任边界)。
|
|
237
|
+
|
|
193
238
|
## 远程访问与安全
|
|
194
239
|
|
|
195
240
|
默认远程地址为 `0.0.0.0:3090`,IP 和端口都可修改。客户端可以使用能到达服务器的 IP 或域名连接。
|
|
@@ -206,9 +251,27 @@ dsh plugin --profile web add ./dsh-remote-bridge-v2.5.2.tgz
|
|
|
206
251
|
- 配对、认证和普通请求分别限流。
|
|
207
252
|
- 审计记录操作类型和对象,但不记录令牌、文件正文或聊天正文。
|
|
208
253
|
- 新增根目录不会自动授权给已经配对的设备。
|
|
254
|
+
- 供应商 `baseURL` 只能指向公网 http(s) 地址;把「已经存有凭据」的供应商换到新 host
|
|
255
|
+
还必须同时提供新的 API key —— 因为内核会带着那把 key 去请求这个地址,这是凭据边界,
|
|
256
|
+
不只是 SSRF。
|
|
209
257
|
|
|
210
258
|
只有来自回环地址的管理界面可以添加根目录、调整设备权限、生成配对码、修改监听设置和永久清理回收站。
|
|
211
259
|
|
|
260
|
+
### 自建 LLM(ollama / LM Studio)
|
|
261
|
+
|
|
262
|
+
私网、回环、链路本地与 CGNAT 的供应商地址**默认拒绝**。要用本机模型服务,在插件配置里
|
|
263
|
+
显式放行:
|
|
264
|
+
|
|
265
|
+
```yaml
|
|
266
|
+
allowPrivateProviderHosts:
|
|
267
|
+
- 127.0.0.1:11434 # 推荐:钉住端口
|
|
268
|
+
- 192.168.1.5 # 裸 host:放行该主机的全部端口
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
只接受精确 `host` 或 `host:port`,**不支持 CIDR**。推荐带端口写法:放行 `192.168.1.5`
|
|
272
|
+
等于放行它上面所有服务,而 ollama 只在 11434。理由与审计字段见
|
|
273
|
+
[docs/project/SECURITY.md](docs/project/SECURITY.md#供应商-baseurl-的地址策略261-起sec-h11)。
|
|
274
|
+
|
|
212
275
|
## 文件边界
|
|
213
276
|
|
|
214
277
|
外部接口只接受 `rootId + relativePath`,不会接收服务器绝对路径。路径还会经过以下检查:
|
|
@@ -283,6 +346,16 @@ Authorization: Bearer DEVICE_TOKEN
|
|
|
283
346
|
|
|
284
347
|
**新增的权限是配对前在「远程访问」里勾选的**:勾什么,生成的配对码就带什么;APP 输入配对码后这些权限随设备一起生效。已在用的设备则到「设备」里点它的「权限」修改。
|
|
285
348
|
|
|
349
|
+
默认勾选是五档 —— `chat.read`、`chat.write`、`files.read`、`files.write`、`settings.read`,**刻意不含** `files.delete` 与 `settings.write`。你仍然可以自己勾上;默认值只是不再替你做这个决定。
|
|
350
|
+
|
|
351
|
+
### 设备名与型号
|
|
352
|
+
|
|
353
|
+
配对那一刻桥并不知道将来是谁来用这个令牌,所以新设备先得到一个**平台无关**的占位名(`Mobile device`)。设备第一次连上来时,桥会从请求里识别平台与型号,把名字自动换成「平台 · 型号」(比如 `HarmonyOS · ICL-AL10`)。
|
|
354
|
+
|
|
355
|
+
平台判定优先看客户端上报的 `X-Dsh-Device: <platform>/<model>` 头,没有就从 `User-Agent` 兜底。**型号只认客户端上报**:OkHttp 与 ArkWeb 的默认 UA 里都没有机型,桥不会去猜 —— 猜错的型号比没有型号更糟。
|
|
356
|
+
|
|
357
|
+
在「设备」页点「权限」可以直接改名。**你手动改过的名字不会再被自动覆盖**;自动生成的名字在设备上报了新身份时仍会更新。设备名只用于显示与排查,不参与任何授权判断。
|
|
358
|
+
|
|
286
359
|
**撤销一台设备会直接把它从数据库里删除**,不是打个标记留着;它的授权随之清除,但审计记录(谁在什么时候撤了谁)会保留。
|
|
287
360
|
|
|
288
361
|
## 升级与卸载
|
|
@@ -340,20 +413,30 @@ pnpm pack
|
|
|
340
413
|
|
|
341
414
|
`pnpm check` 覆盖类型检查、文档一致性与测试;当前 **67 个测试文件 / 557 个用例**,外加 **8 个浏览器层用例**(Playwright 实测量出的计算样式,含 375px 窄屏契约),全部通过。
|
|
342
415
|
|
|
343
|
-
`npm pack` 的产物约 **0.
|
|
416
|
+
`npm pack` 的产物约 **0.64 MB**(v2.6.1 实测 669,434 字节,SHA-256 `9bf0189b…2cd35`):2.2.0 删除独立页及其编辑器依赖、并关闭 source map 之后从 1.72 MB 降下来,之后随投影透传、权限 BFF 与五维加固小幅增长。
|
|
344
417
|
|
|
345
418
|
v2.5.0 在 DSH **0.2.1-rc.2** 上完成真机验证:14/14 宿主服务全部可注入、三个 jobs 端点实测通过、
|
|
346
419
|
权限档位目录返回内核真实三档。开发基线为上游 `dsh-v0.2.1-rc.2`(commit `687f282e8625c6b8d11ff58211a90524be2e4317`),
|
|
347
420
|
兼容范围见上文 `engines.dsh`。CI 覆盖 Windows、Ubuntu 和 macOS。
|
|
348
421
|
|
|
349
|
-
**v2.
|
|
350
|
-
(含 375px 窄屏契约)、`typecheck`/`docs:check
|
|
351
|
-
|
|
352
|
-
|
|
422
|
+
**v2.6.1 的验证状态**:74 个测试文件 / 682 个用例全绿、8 个 Playwright 浏览器用例
|
|
423
|
+
(含 375px 窄屏契约)、`typecheck`/`docs:check`/构建通过。SEC-H11 的攻击链是**用实测**
|
|
424
|
+
确认的(起一个假 LLM 端点,抓到内核把真实 `Authorization: Bearer` 打过去),修复后的
|
|
425
|
+
四道闸门各有回归断言;设备身份的三个 spec(27 + 13 + 12 例)覆盖 UA 判定顺序、
|
|
426
|
+
「`declared` 永不覆盖」与老表结构迁移幂等。
|
|
353
427
|
|
|
354
428
|
## 项目信息
|
|
355
429
|
|
|
356
|
-
- 当前版本:`v2.
|
|
430
|
+
- 当前版本:`v2.6.1`
|
|
431
|
+
- **v2.6.1 是安全修复 + 设备身份版**:修掉一个**高危**问题 —— 供应商 `baseURL` 的三个入口
|
|
432
|
+
(新建 / 修改 / 探测模型)此前不做任何校验,持有 `settings.write` 的设备可以把 baseURL
|
|
433
|
+
指向自己的服务器,内核会带着**该供应商的真实 API key** 去请求 `/models`,等于凭据直接外泄;
|
|
434
|
+
持久化变体更糟:改完 baseURL 之后正常聊天都会把 key 发过去。现在三道闸门(协议白名单、
|
|
435
|
+
私网默认拒绝 + `allowPrivateProviderHosts` 显式放行、换 host 且已有凭据时必须同时给新 key),
|
|
436
|
+
配对默认权限也从七档收到五档(不再默认给 `settings.write` 与 `files.delete`)。
|
|
437
|
+
另修「设备名永远是『Android 设备』」:桥现在从 `X-Dsh-Device` 头(UA 兜底)识别平台与型号,
|
|
438
|
+
自动把占位名换成「平台 · 型号」,并新增改名通道(手动改过的名字永不被自动覆盖)。
|
|
439
|
+
- **v2.6.0 是通用设置面 + IM 入口版**:新增「按插件划分的设置命名空间」两个端点(手机端照 schema 自建表单,新装插件无需改 App),以及「人已出门、电脑还锁着」时用 IM 一句话开隧道并收回配对配置的双入口(宿主命令 `/tunnel` + 工具 `open_tunnel_config`);另加一键清理 N 天未使用设备、移除审计页,并把「激活期降级保护」补上(apply 尾部抛错不再拆掉整条插件上下文)。
|
|
357
440
|
- **v2.5.2 是全量完善版**:落地五维审计(安全/功能/性能/界面/按钮反馈)——含高危的
|
|
358
441
|
`debug/host-services` 免鉴权信息泄露修复、限流双桶、广播背压、弱 ETag、审计保留策略等,
|
|
359
442
|
并把「一键开隧道并生成令牌」做成同一次请求内搭车完成;隧道运行时管理面安全锁定,
|
package/docs/api/asyncapi.yaml
CHANGED
package/docs/api/openapi.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
openapi: 3.1.0
|
|
2
2
|
info:
|
|
3
3
|
title: DeepSeek Harness Mobile API
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.6.1
|
|
5
5
|
description: Versioned file and chat BFF for mobile and third-party DSH clients. HTTP and HTTPS are both supported.
|
|
6
6
|
servers:
|
|
7
7
|
- url: http://127.0.0.1:3090/api/v1
|
|
@@ -24,7 +24,7 @@ paths:
|
|
|
24
24
|
properties:
|
|
25
25
|
ok: { type: boolean, const: true }
|
|
26
26
|
version: { type: string, const: v1 }
|
|
27
|
-
pluginVersion: { type: string, const: 2.
|
|
27
|
+
pluginVersion: { type: string, const: 2.6.1 }
|
|
28
28
|
/debug/host-services:
|
|
29
29
|
get:
|
|
30
30
|
operationId: hostServices
|
|
@@ -506,7 +506,16 @@ paths:
|
|
|
506
506
|
'403': { $ref: '#/components/responses/Error' }
|
|
507
507
|
post:
|
|
508
508
|
operationId: createCustomProvider
|
|
509
|
-
description:
|
|
509
|
+
description: |
|
|
510
|
+
Creates a fully custom llm-pi-ai provider using the protocols reported by
|
|
511
|
+
ProviderSettings.customProvider.
|
|
512
|
+
|
|
513
|
+
`baseURL` must be an absolute http(s) URL. Private, loopback, link-local and
|
|
514
|
+
CGNAT destinations are rejected with `403 PROVIDER_BASE_URL_FORBIDDEN` unless the
|
|
515
|
+
deployment lists the host in `allowPrivateProviderHosts` (exact `host` or
|
|
516
|
+
`host:port`, no CIDR). The kernel sends the provider's stored API key to this
|
|
517
|
+
address, so the check is what keeps a paired device from redirecting credentials.
|
|
518
|
+
See SECURITY.md ("供应商 baseURL 的地址策略").
|
|
510
519
|
requestBody:
|
|
511
520
|
required: true
|
|
512
521
|
content:
|
|
@@ -519,6 +528,7 @@ paths:
|
|
|
519
528
|
application/json:
|
|
520
529
|
schema: { $ref: '#/components/schemas/ProviderSettings' }
|
|
521
530
|
'400': { $ref: '#/components/responses/Error' }
|
|
531
|
+
'403': { $ref: '#/components/responses/Error' }
|
|
522
532
|
'409': { $ref: '#/components/responses/Error' }
|
|
523
533
|
/settings/plugins:
|
|
524
534
|
get:
|
|
@@ -579,6 +589,68 @@ paths:
|
|
|
579
589
|
content:
|
|
580
590
|
application/json:
|
|
581
591
|
schema: { $ref: '#/components/schemas/ModelCatalog' }
|
|
592
|
+
/settings/namespaces:
|
|
593
|
+
get:
|
|
594
|
+
operationId: listSettingsNamespaces
|
|
595
|
+
summary: Read every DSH plugin settings namespace
|
|
596
|
+
description: |
|
|
597
|
+
Returns the host's per-plugin settings document as one entry per namespace, so a
|
|
598
|
+
mobile client can build a form from `schema` without a dedicated endpoint per
|
|
599
|
+
plugin. Requires `settings.read`.
|
|
600
|
+
|
|
601
|
+
The top-level `writable` mirrors the host settings document. Each entry's
|
|
602
|
+
`writable` is that value AND the bridge's write policy: namespaces starting with
|
|
603
|
+
`ui-` or ending in `locale` are writable by default; high-risk ones (`llm-*`,
|
|
604
|
+
`permission`, `agent-default-model`, `subagent-*`, …) are read-only unless the
|
|
605
|
+
deployment explicitly allows them through `settingsWriteNamespaces`. When an
|
|
606
|
+
entry is not writable, `reason` says why: `HOST_READONLY` (the whole settings
|
|
607
|
+
document is read-only) or `HIGH_RISK_NAMESPACE` (write policy).
|
|
608
|
+
|
|
609
|
+
Secret values never leave the host: the kernel already redacts fields declared as
|
|
610
|
+
secrets, and the bridge additionally masks string values whose key matches
|
|
611
|
+
`apiKey|secret|token|password|credential|apiKeyEnv` with `***`.
|
|
612
|
+
responses:
|
|
613
|
+
'200':
|
|
614
|
+
description: All namespaces, with secret values redacted and per-namespace writability.
|
|
615
|
+
content:
|
|
616
|
+
application/json:
|
|
617
|
+
schema: { $ref: '#/components/schemas/SettingsNamespaces' }
|
|
618
|
+
'403': { $ref: '#/components/responses/Error' }
|
|
619
|
+
/settings/namespaces/{ns}:
|
|
620
|
+
parameters:
|
|
621
|
+
- name: ns
|
|
622
|
+
in: path
|
|
623
|
+
required: true
|
|
624
|
+
schema: { type: string }
|
|
625
|
+
description: Namespace id (a plugin name such as `ui-chat`).
|
|
626
|
+
patch:
|
|
627
|
+
operationId: mutateSettingsNamespace
|
|
628
|
+
description: |
|
|
629
|
+
Applies path-addressed `set` / `unset` operations to one namespace, mirroring the
|
|
630
|
+
kernel's `settings.mutate`. Requires `settings.write`; at most 32 operations per
|
|
631
|
+
request. A successful mutation is audited as `settings.namespace.mutate` with the
|
|
632
|
+
touched field paths only — never the values.
|
|
633
|
+
|
|
634
|
+
Only namespaces allowed by the write policy can be changed; others answer
|
|
635
|
+
403 `SETTINGS_NAMESPACE_READONLY`. `expectedRevision` is optimistic concurrency:
|
|
636
|
+
a mismatch answers 409 `SETTINGS_CONFLICT`; an invalid ops body answers
|
|
637
|
+
400 `SETTINGS_OPS_INVALID`; a missing host settings service answers
|
|
638
|
+
503 `SETTINGS_UNAVAILABLE`.
|
|
639
|
+
requestBody:
|
|
640
|
+
required: true
|
|
641
|
+
content:
|
|
642
|
+
application/json:
|
|
643
|
+
schema: { $ref: '#/components/schemas/SettingsNamespaceOps' }
|
|
644
|
+
responses:
|
|
645
|
+
'200':
|
|
646
|
+
description: The mutated namespace, in the same shape as one entry of `SettingsNamespaces.namespaces`.
|
|
647
|
+
content:
|
|
648
|
+
application/json:
|
|
649
|
+
schema: { $ref: '#/components/schemas/SettingsNamespaceSummary' }
|
|
650
|
+
'400': { $ref: '#/components/responses/Error' }
|
|
651
|
+
'403': { $ref: '#/components/responses/Error' }
|
|
652
|
+
'409': { $ref: '#/components/responses/Error' }
|
|
653
|
+
'503': { $ref: '#/components/responses/Error' }
|
|
582
654
|
/settings/providers/{providerId}:
|
|
583
655
|
parameters:
|
|
584
656
|
- name: providerId
|
|
@@ -587,7 +659,15 @@ paths:
|
|
|
587
659
|
schema: { type: string }
|
|
588
660
|
patch:
|
|
589
661
|
operationId: updateProvider
|
|
590
|
-
description:
|
|
662
|
+
description: |
|
|
663
|
+
Updates provider settings and optionally writes an API key to the DSH credential store.
|
|
664
|
+
|
|
665
|
+
`baseURL` goes through the same address policy as creation. In addition, moving a
|
|
666
|
+
provider that already holds a credential to a different host requires supplying a
|
|
667
|
+
new `apiKey` in the same patch (`403 PROVIDER_BASE_URL_REPOINT_FORBIDDEN`) —
|
|
668
|
+
otherwise a paired device could point a live key at its own public domain, which
|
|
669
|
+
the private-address rule cannot catch. Changing models, the display name, or the
|
|
670
|
+
path within the same host is unaffected.
|
|
591
671
|
requestBody:
|
|
592
672
|
required: true
|
|
593
673
|
content:
|
|
@@ -613,6 +693,11 @@ paths:
|
|
|
613
693
|
/settings/providers/{providerId}/discover:
|
|
614
694
|
post:
|
|
615
695
|
operationId: discoverProviderModels
|
|
696
|
+
description: |
|
|
697
|
+
Fetches `GET {baseURL}/models` through the kernel, carrying the provider's stored
|
|
698
|
+
credential. `baseURL` here is a temporary override that is NOT persisted, so a
|
|
699
|
+
rejected address never reaches the config — but it does reach the network unless
|
|
700
|
+
the address policy allows it. Same rules as creation.
|
|
616
701
|
parameters:
|
|
617
702
|
- name: providerId
|
|
618
703
|
in: path
|
|
@@ -640,6 +725,8 @@ paths:
|
|
|
640
725
|
models:
|
|
641
726
|
type: array
|
|
642
727
|
items: { $ref: '#/components/schemas/ProviderModel' }
|
|
728
|
+
'400': { $ref: '#/components/responses/Error' }
|
|
729
|
+
'403': { $ref: '#/components/responses/Error' }
|
|
643
730
|
/chat/sessions:
|
|
644
731
|
get:
|
|
645
732
|
operationId: listChatSessions
|
|
@@ -1350,6 +1437,28 @@ components:
|
|
|
1350
1437
|
rootIds:
|
|
1351
1438
|
type: array
|
|
1352
1439
|
items: { type: string }
|
|
1440
|
+
platform:
|
|
1441
|
+
type: string
|
|
1442
|
+
description: >-
|
|
1443
|
+
Platform the device reported, or the one inferred from its
|
|
1444
|
+
User-Agent when it reported nothing. Absent for devices that never
|
|
1445
|
+
connected since the bridge started collecting it. Informational
|
|
1446
|
+
only — it is client-declared and never takes part in authorization.
|
|
1447
|
+
enum: [android, harmonyos, ios, ipados, web, windows, macos, linux]
|
|
1448
|
+
model:
|
|
1449
|
+
type: string
|
|
1450
|
+
description: >-
|
|
1451
|
+
Model the device reported. Often absent: neither okhttp nor ArkWeb
|
|
1452
|
+
puts a model in its default User-Agent, and the bridge deliberately
|
|
1453
|
+
does not guess — a wrong model is worse than no model.
|
|
1454
|
+
nameSource:
|
|
1455
|
+
type: string
|
|
1456
|
+
description: >-
|
|
1457
|
+
Where `name` came from. `default` (pairing placeholder) and
|
|
1458
|
+
`detected` (auto-generated from the reported identity) may still be
|
|
1459
|
+
replaced when the device reports a different identity; `declared`
|
|
1460
|
+
means a human set it and it is never overwritten automatically.
|
|
1461
|
+
enum: [default, detected, declared]
|
|
1353
1462
|
FileEntry:
|
|
1354
1463
|
type: object
|
|
1355
1464
|
required: [name, path, kind, size, modifiedAt, writable]
|
|
@@ -1624,6 +1733,83 @@ components:
|
|
|
1624
1733
|
providers:
|
|
1625
1734
|
type: array
|
|
1626
1735
|
items: { $ref: '#/components/schemas/Provider' }
|
|
1736
|
+
SettingsNamespaceSecret:
|
|
1737
|
+
type: object
|
|
1738
|
+
description: One secret slot reported by the host. Only its path and whether it is set are exposed, never the value.
|
|
1739
|
+
required: [path, set]
|
|
1740
|
+
properties:
|
|
1741
|
+
path:
|
|
1742
|
+
type: array
|
|
1743
|
+
items: { type: string }
|
|
1744
|
+
set: { type: boolean }
|
|
1745
|
+
SettingsNamespaceSummary:
|
|
1746
|
+
type: object
|
|
1747
|
+
description: One DSH settings namespace as seen by a remote client, with secret values redacted.
|
|
1748
|
+
required: [ns, revision, value, writable]
|
|
1749
|
+
properties:
|
|
1750
|
+
ns: { type: string, examples: [ui-chat, ui-theme] }
|
|
1751
|
+
revision: { type: integer, minimum: 0 }
|
|
1752
|
+
schema:
|
|
1753
|
+
type: object
|
|
1754
|
+
additionalProperties: true
|
|
1755
|
+
description: Kernel schema tree for form generation; its shape is kernel-defined.
|
|
1756
|
+
value:
|
|
1757
|
+
description: Effective value (base merged with user overrides), recursively redacted.
|
|
1758
|
+
base:
|
|
1759
|
+
description: Built-in default value, recursively redacted.
|
|
1760
|
+
user:
|
|
1761
|
+
description: User overrides only, recursively redacted.
|
|
1762
|
+
autoGenerate:
|
|
1763
|
+
type: object
|
|
1764
|
+
additionalProperties: true
|
|
1765
|
+
description: Kernel-reported fields that are generated automatically.
|
|
1766
|
+
applies:
|
|
1767
|
+
type: object
|
|
1768
|
+
additionalProperties: true
|
|
1769
|
+
description: Kernel-reported applicability conditions for this namespace.
|
|
1770
|
+
secrets:
|
|
1771
|
+
type: array
|
|
1772
|
+
items: { $ref: '#/components/schemas/SettingsNamespaceSecret' }
|
|
1773
|
+
writable:
|
|
1774
|
+
type: boolean
|
|
1775
|
+
description: The host document is writable AND this namespace passes the bridge's write policy.
|
|
1776
|
+
reason:
|
|
1777
|
+
type: string
|
|
1778
|
+
enum: [HOST_READONLY, HIGH_RISK_NAMESPACE]
|
|
1779
|
+
description: Present only when `writable` is false.
|
|
1780
|
+
SettingsNamespaces:
|
|
1781
|
+
type: object
|
|
1782
|
+
required: [writable, namespaces]
|
|
1783
|
+
properties:
|
|
1784
|
+
writable:
|
|
1785
|
+
type: boolean
|
|
1786
|
+
description: Whether the host settings document itself is writable.
|
|
1787
|
+
namespaces:
|
|
1788
|
+
type: array
|
|
1789
|
+
items: { $ref: '#/components/schemas/SettingsNamespaceSummary' }
|
|
1790
|
+
SettingsNamespaceOps:
|
|
1791
|
+
type: object
|
|
1792
|
+
description: Path-addressed operations and optional optimistic-concurrency revision.
|
|
1793
|
+
required: [ops]
|
|
1794
|
+
properties:
|
|
1795
|
+
ops:
|
|
1796
|
+
type: array
|
|
1797
|
+
minItems: 1
|
|
1798
|
+
maxItems: 32
|
|
1799
|
+
items:
|
|
1800
|
+
oneOf:
|
|
1801
|
+
- type: object
|
|
1802
|
+
required: [op, path, value]
|
|
1803
|
+
properties:
|
|
1804
|
+
op: { type: string, const: set }
|
|
1805
|
+
path: { type: array, items: { type: string } }
|
|
1806
|
+
value: {}
|
|
1807
|
+
- type: object
|
|
1808
|
+
required: [op, path]
|
|
1809
|
+
properties:
|
|
1810
|
+
op: { type: string, const: unset }
|
|
1811
|
+
path: { type: array, items: { type: string } }
|
|
1812
|
+
expectedRevision: { type: integer, minimum: 0 }
|
|
1627
1813
|
ProviderPatch:
|
|
1628
1814
|
type: object
|
|
1629
1815
|
properties:
|
|
@@ -46,6 +46,12 @@ data class DeviceView(
|
|
|
46
46
|
val name: String,
|
|
47
47
|
val scopes: List<String>,
|
|
48
48
|
val rootIds: List<String>,
|
|
49
|
+
/** 平台:`android` / `harmonyos` / `ios` / …。老桥与从未连过的设备没有这个字段。 */
|
|
50
|
+
val platform: String? = null,
|
|
51
|
+
/** 型号。桥刻意不猜(okhttp/ArkWeb 默认 UA 里没有机型),所以多数时候是 null。 */
|
|
52
|
+
val model: String? = null,
|
|
53
|
+
/** `name` 的来源:`default` / `detected` / `declared`。null 等价于 `declared`。 */
|
|
54
|
+
val nameSource: String? = null,
|
|
49
55
|
)
|
|
50
56
|
|
|
51
57
|
@Serializable
|