dsh-ssh-tunnel 1.0.1 → 1.1.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.
- package/CHANGELOG.md +70 -0
- package/README.md +40 -63
- package/README_en.md +38 -60
- package/cordis.patch.yml +4 -4
- package/lib/client.js +1699 -1493
- package/lib/index.js +1739 -1611
- package/lib/session.js +616 -525
- package/lib/shared/args.js +23 -0
- package/lib/shared/host-key.js +32 -32
- package/lib/shared/host-summary.js +75 -75
- package/lib/shared/http-trust.js +59 -59
- package/lib/shared/path.js +21 -22
- package/lib/shared/persist.js +41 -41
- package/lib/shared/session-auth.js +47 -39
- package/lib/shared/session-policy.js +151 -125
- package/lib/shared/shell-buffer.js +10 -10
- package/lib/shared/vendor.js +49 -49
- package/package.json +88 -88
- package/scripts/install.ps1 +190 -93
- package/scripts/install.sh +276 -226
- package/scripts/lib/bundle-check.cjs +108 -0
- package/scripts/lib/strip-mount.cjs +114 -0
- package/scripts/lib/ws-exclude.cjs +143 -0
- package/scripts/portal-probe.mjs +43 -5
- package/scripts/smoke-test.mjs +899 -511
- package/scripts/sync-to-dsh.sh +87 -5
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,64 @@ All notable changes to this project are documented in this file.
|
|
|
10
10
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
11
11
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
12
12
|
|
|
13
|
+
## [1.1.0] - 2026-10-09
|
|
14
|
+
|
|
15
|
+
### 变更
|
|
16
|
+
|
|
17
|
+
- **客户端面板改挂 DSH 官方右侧栏。** 标签页类型经 `sidebarRightTabs.register` 注册,正文与标题分别占用官方槽位 `sidebar.right.pane.tab` 与 `sidebar.right.pane.tab.title`;引导页入口框由 `guide` 条目渲染(order 44)。注册不再经由第三方侧栏宿主插件:`dsh-better-sidebar` 的 peer、`dsh.client.inject` 条目与 keywords 一并移除。
|
|
18
|
+
- **客户端不再传项目 cwd 提示。** `getProjectContext` 只带 `sessionId`,项目路径由宿主从会话头(`ctx.sessions.get(sessionId).header.cwd`)解析;随之删除宿主注入的 `scope.sessionId` / `scope.cwd` 等字段,会话 id 改取官方席位平铺注入的 `sessionId`。
|
|
19
|
+
- **DSH 兼容下限抬到 0.2.0-rc.2。** `engines.dsh` 与 `@deepseek-ai/dsh-client-locale` peer 收敛为单段 `>=0.2.0-rc.2 <0.3.0-0`,并新增同区间的 `@deepseek-ai/dsh-client-ui-sidebar-right` peer。
|
|
20
|
+
- **官方右侧栏的标签页不自动常驻。** 用户从标签条「+」的引导页入口打开面板;布局按会话持久化,新会话第一次需各开一次。
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- **The client panel is mounted on the official DSH right sidebar.** The tab type registers through `sidebarRightTabs.register`, and the body and title take the official seats `sidebar.right.pane.tab` and `sidebar.right.pane.tab.title`; the guide-page entry box renders from the `guide` entry (order 44). Registration no longer goes through a third-party sidebar host plugin: the `dsh-better-sidebar` peer, its `dsh.client.inject` entry and its keywords are removed.
|
|
25
|
+
- **The client no longer sends a project cwd hint.** `getProjectContext` carries only `sessionId`, and the host resolves the project path from the session header (`ctx.sessions.get(sessionId).header.cwd`); the host-injected `scope.sessionId` / `scope.cwd` fields go away, and the session id now comes from the flat `sessionId` prop the official seat injects.
|
|
26
|
+
- **The DSH compatibility floor moves to 0.2.0-rc.2.** `engines.dsh` and the `@deepseek-ai/dsh-client-locale` peer collapse into the single segment `>=0.2.0-rc.2 <0.3.0-0`, and a new `@deepseek-ai/dsh-client-ui-sidebar-right` peer declares the same range.
|
|
27
|
+
- **The official right-sidebar tab is not resident by default.** Users open the panel from the "+" guide page in the tab strip; layout persists per session, so each new session needs one open.
|
|
28
|
+
|
|
29
|
+
## [1.0.2] - 2026-10-08
|
|
30
|
+
|
|
31
|
+
### 新增
|
|
32
|
+
|
|
33
|
+
- **交互式输出读取采用 seq 游标协议。** 侧栏轮询接口 `shellRead` 的响应带 `chunk` / `since` / `seq` / `baseSeq` / `dropped` / `chunkTruncated`:客户端携带上次读到的 `since`,宿主返回其后的新增输出;环形缓冲(512 KiB)淘汰后 `baseSeq` 前移且 `dropped: true`,客户端据此全量重取,避免缓冲滚动造成输出缺口。单次响应的 `chunk` 上限 256 KiB。
|
|
34
|
+
- **API 错误返回结构化错误码。** 错误响应体带 `code` 字段,并按语义映射 HTTP 状态:`bad_request` 400、`forbidden` 403、`not_found` 404、`conflict` 409、`gone` 410、`payload_too_large` 413;未映射的错误保持 500。
|
|
35
|
+
- **主机密钥确认提示有时效与上限。** 待确认提示 5 分钟过期,至多保留 50 条(超出时淘汰最旧),按项目隔离。
|
|
36
|
+
|
|
37
|
+
### 变更
|
|
38
|
+
|
|
39
|
+
- **安装脚本以 `--profile` 为必填参数。** `scripts/install.sh` 与 `scripts/install.ps1`:目标 profile 必填且无默认值,缺失或不存在的 profile 报错并列出实存 profile(退出码 2)。`--fix-profile` / `-FixProfile` 为可选开关:仅在该开关下补写 profile `pnpm-workspace.yaml` 的 `minimumReleaseAgeExclude`(幂等)并清理 profile `cordis.patch.yml` 里旧版写入的手动挂载,写入后回读断言,失败回滚并以非零码退出。bash 试运行为 `--dry-run`,PowerShell 为 `-DryRun`。`PATH` 缺少 `dsh` 命令时的 `npx` 兜底先打印将执行的命令并要求确认(`DSH_INSTALL_YES=1` 跳过)。安装后读取 profile 的 `ignoredBuilds`,发现条目时打印 `allowBuilds` 豁免指引。`scripts/sync-to-dsh.sh` 支持 `--dry-run`。
|
|
40
|
+
- **兼容区间按预发布线拆段。** `engines.dsh` 与 `@deepseek-ai/dsh-client-locale` peer 声明为 `>=0.1.7-rc.2 <0.2.0-0 || >=0.2.0-rc.1 <0.3.0-0`,0.1.7 线与 0.2 线各占一段。
|
|
41
|
+
- **exec 结果如实结算。** 返回值带 `exitKnown` / `code` / `signal` / `connectionDropped`:正常退出 `exitKnown: true` 且 `code` 为退出码;按信号终止时 `signal` 为信号名;连接被切断时 `code` 为 `null` 且 `connectionDropped: true`。
|
|
42
|
+
- **`max_bytes` 按字节钳制。** exec 与 `sftp_read_text` 的输出上限钳制在 1024–1048576 字节(默认 exec 262144、`sftp_read_text` 524288),输出按字节截断并在结果中置 `truncated: true`。
|
|
43
|
+
- **客户端注入清单补齐到达顺序边。** `dsh.client.inject` 增加 `dsh-better-sidebar`;`exports` 放行 `./cordis.patch.yml`;`files` 放行 `scripts/lib/*.cjs`(安装脚本共享逻辑)。
|
|
44
|
+
- **键盘交互式认证不支持。** 该类主机在连接、重连与 `SSHManager` 中被拒绝(`credential=unsupported`),提示改用密码或私钥。
|
|
45
|
+
- **文档更新。** 安全章节改为客观口径(本机回环、无鉴权 token、本机进程视为已授权用户)、补 seq 游标协议与超时/上限声明、0700/0600 加 POSIX 平台限定。
|
|
46
|
+
|
|
47
|
+
### 安全
|
|
48
|
+
|
|
49
|
+
- **授权检查全程 fail-closed。** 建立连接前必须已有该项目授权;写类操作在发起前与结果回报前各复检一次;授权撤销即时生效——受影响的存活会话被关闭、在飞输出被丢弃并以 `not authorized` 失败、下载中的本地文件被删除。本地工作区根取不到时拒绝操作,可用环境变量 `DSH_SSH_TUNNEL_WORKSPACE_ROOT` 显式指定。
|
|
50
|
+
|
|
51
|
+
### Added
|
|
52
|
+
|
|
53
|
+
- **Interactive output is read through a seq cursor protocol.** The sidebar polling API `shellRead` returns `chunk` / `since` / `seq` / `baseSeq` / `dropped` / `chunkTruncated`: the client sends the last `since` it has seen and the host returns the newer output; ring-buffer eviction (512 KiB) moves `baseSeq` forward and sets `dropped: true`, telling the client to refetch in full, which prevents output gaps from buffer rollover. A single response caps `chunk` at 256 KiB.
|
|
54
|
+
- **API errors carry structured codes.** Error response bodies include a `code` field mapped to HTTP statuses by meaning: `bad_request` 400, `forbidden` 403, `not_found` 404, `conflict` 409, `gone` 410, `payload_too_large` 413; unmapped errors stay 500.
|
|
55
|
+
- **Host key confirmation prompts have a lifetime and a cap.** Pending prompts expire after 5 minutes, at most 50 are kept (oldest evicted beyond that), and they are isolated per project.
|
|
56
|
+
|
|
57
|
+
### Changed
|
|
58
|
+
|
|
59
|
+
- **The install scripts require `--profile`.** `scripts/install.sh` and `scripts/install.ps1`: the target profile is required with no default; a missing or nonexistent profile fails with a list of the profiles that do exist (exit code 2). `--fix-profile` / `-FixProfile` is an optional switch: only with it does the script add the plugin to the profile's `pnpm-workspace.yaml` (`minimumReleaseAgeExclude`, idempotent) and remove the hand-written mount older versions put into the profile's `cordis.patch.yml`; writes are read back and asserted, and a failure rolls the change back and exits non-zero. The bash dry-run flag is `--dry-run`, the PowerShell one is `-DryRun`. When `dsh` is missing from `PATH`, the `npx` fallback prints the command it is about to execute and asks for confirmation (`DSH_INSTALL_YES=1` skips it). After installing, the script reads the profile's `ignoredBuilds` and prints an `allowBuilds` exemption recipe when entries are found. `scripts/sync-to-dsh.sh` supports `--dry-run`.
|
|
60
|
+
- **The compatibility range is split per prerelease line.** `engines.dsh` and the `@deepseek-ai/dsh-client-locale` peer declare `>=0.1.7-rc.2 <0.2.0-0 || >=0.2.0-rc.1 <0.3.0-0` — the 0.1.7 line and the 0.2 line each get their own segment.
|
|
61
|
+
- **exec results are settled faithfully.** Results carry `exitKnown` / `code` / `signal` / `connectionDropped`: a normal exit sets `exitKnown: true` with the exit code in `code`; a signal death returns the signal name in `signal`; a cut connection returns `code: null` with `connectionDropped: true`.
|
|
62
|
+
- **`max_bytes` is clamped in bytes.** The output cap for exec and `sftp_read_text` is clamped to 1024–1048576 bytes (defaults: 262144 for exec, 524288 for `sftp_read_text`); output is truncated on byte boundaries and the result sets `truncated: true`.
|
|
63
|
+
- **The client inject list completes the arrival-order edge.** `dsh.client.inject` adds `dsh-better-sidebar`; `exports` admits `./cordis.patch.yml`; `files` admits `scripts/lib/*.cjs` (installer shared logic).
|
|
64
|
+
- **keyboard-interactive auth is not supported.** Such hosts are refused on connect, reconnect and in `SSHManager` (`credential=unsupported`) with a message to switch to password or private key.
|
|
65
|
+
- **Documentation updates.** The security section states the trust boundary objectively (local loopback, no authentication token, local processes treated as authorized users), the seq cursor protocol and timeout/limit declarations are added, and the 0700/0600 statements carry a POSIX platform qualifier.
|
|
66
|
+
|
|
67
|
+
### Security
|
|
68
|
+
|
|
69
|
+
- **Authorization checks fail closed end to end.** Connecting requires an existing project grant; write operations re-check the grant once before being issued and once before the result is delivered; a revocation takes effect immediately — affected live sessions are closed, in-flight output is discarded and the call fails with `not authorized`, and a local file mid-download is deleted. Operations are refused when no local workspace root can be resolved; the environment variable `DSH_SSH_TUNNEL_WORKSPACE_ROOT` can set one explicitly.
|
|
70
|
+
|
|
13
71
|
## [1.0.1] - 2026-10-04
|
|
14
72
|
|
|
15
73
|
### 新增
|
|
@@ -317,3 +375,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
317
375
|
### Added
|
|
318
376
|
|
|
319
377
|
- Initial permanent plugin scaffold
|
|
378
|
+
|
|
379
|
+
[1.0.2]: https://github.com/OMSociety/dsh-ssh-tunnel/compare/v1.0.1...v1.0.2
|
|
380
|
+
[1.0.1]: https://github.com/OMSociety/dsh-ssh-tunnel/compare/v1.0.0...v1.0.1
|
|
381
|
+
[1.0.0]: https://github.com/OMSociety/dsh-ssh-tunnel/compare/v0.4.6...v1.0.0
|
|
382
|
+
[0.4.6]: https://github.com/OMSociety/dsh-ssh-tunnel/compare/v0.4.5...v0.4.6
|
|
383
|
+
[0.4.5]: https://github.com/OMSociety/dsh-ssh-tunnel/compare/v0.4.3...v0.4.5
|
|
384
|
+
[0.4.3]: https://github.com/OMSociety/dsh-ssh-tunnel/compare/v0.4.2...v0.4.3
|
|
385
|
+
[0.4.2]: https://github.com/OMSociety/dsh-ssh-tunnel/compare/v0.4.1...v0.4.2
|
|
386
|
+
[0.4.1]: https://github.com/OMSociety/dsh-ssh-tunnel/compare/v0.4.0...v0.4.1
|
|
387
|
+
[0.4.0]: https://github.com/OMSociety/dsh-ssh-tunnel/compare/v0.3.11...v0.4.0
|
|
388
|
+
[0.3.11]: https://github.com/OMSociety/dsh-ssh-tunnel/compare/v0.3.7...v0.3.11
|
|
389
|
+
[0.3.7]: https://github.com/OMSociety/dsh-ssh-tunnel/tree/v0.3.7
|
package/README.md
CHANGED
|
@@ -7,19 +7,19 @@
|
|
|
7
7
|
<p>模型用 <strong>SSHManager</strong> 工具执行命令、传文件;你在侧栏管主机与授权。<strong>密钥不进模型上下文</strong>。</p>
|
|
8
8
|
|
|
9
9
|
<p>
|
|
10
|
-
<a href="https://
|
|
11
|
-
<
|
|
10
|
+
<a href="https://www.npmjs.com/package/dsh-ssh-tunnel"><img src="https://img.shields.io/npm/v/dsh-ssh-tunnel?label=version&color=4f6ef7" alt="Version"></a>
|
|
11
|
+
<img src="https://img.shields.io/badge/DSH-%3E%3D0.2.0--rc.2%20%3C0.3.0--0-4f6ef7" alt="DSH">
|
|
12
12
|
<a href="LICENSE"><img src="https://img.shields.io/github/license/OMSociety/dsh-ssh-tunnel?color=4f6ef7" alt="License"></a>
|
|
13
13
|
<a href="https://github.com/OMSociety/dsh-ssh-tunnel/stargazers"><img src="https://img.shields.io/github/stars/OMSociety/dsh-ssh-tunnel?color=4f6ef7" alt="Stars"></a>
|
|
14
14
|
<a href="https://github.com/OMSociety/dsh-ssh-tunnel/issues"><img src="https://img.shields.io/github/issues/OMSociety/dsh-ssh-tunnel?color=4f6ef7" alt="Issues"></a>
|
|
15
15
|
</p>
|
|
16
16
|
|
|
17
|
-
<a href="#这是什么">这是什么</a> • <a href="#核心特性">核心特性</a> • <a href="
|
|
17
|
+
<a href="#这是什么">这是什么</a> • <a href="#核心特性">核心特性</a> • <a href="#安装方式">安装方式</a> • <a href="#侧栏">侧栏</a> • <a href="#模型工具">模型工具</a> • <a href="#安全">安全</a> • <a href="#开发">开发</a> • <a href="#许可证与作者">许可证与作者</a>
|
|
18
18
|
</div>
|
|
19
19
|
|
|
20
20
|
## 这是什么
|
|
21
21
|
|
|
22
|
-
**
|
|
22
|
+
**DSH SSH Tunnel** 是 DeepSeek Harness 的社区插件,挂在 **DSH 官方右侧栏**的标签页上:把多台 SSH 主机收进一个**主机库**,按项目授权,然后在中央面板里开**交互式终端**(xterm)或**双栏 SFTP**。
|
|
23
23
|
|
|
24
24
|
它**不会**把全局 `fs` / `subprocess` 换成一个远程盘:远端操作只发生在你显式调用的 `SSHManager` 工具与面板里,本地文件与远端文件始终是两侧分明的东西。
|
|
25
25
|
|
|
@@ -29,8 +29,6 @@
|
|
|
29
29
|
|
|
30
30
|
**产品形态与部分 UX 参考开源项目 [LiveAgent](https://github.com/thirsty5034/LiveAgent)**(多机 SSH 主机库、按项目授权、侧栏隧道管理、中央终端 / SFTP 等)。
|
|
31
31
|
|
|
32
|
-
本仓库是 **DSH 原生实现**(Cordis host/client、`dsh-better-sidebar` Tab、`SSHManager` 工具、DSH 本地密钥布局),**不是** LiveAgent 的 git fork,也**不**内嵌 LiveAgent 源码。对照设计时请遵守 LiveAgent 自身许可证。
|
|
33
|
-
|
|
34
32
|
## 核心特性
|
|
35
33
|
|
|
36
34
|
| 特性 | 说明 |
|
|
@@ -44,39 +42,16 @@
|
|
|
44
42
|
| **本地路径守卫** | 上传 / 下载 / 列举 / 删除的本机路径限制在**项目工作区根**内,词法 **与** realpath 双重校验 |
|
|
45
43
|
| **界面双语** | 侧栏与面板随 DSH 界面语言在中文 / 英文间即时切换 |
|
|
46
44
|
|
|
47
|
-
##
|
|
48
|
-
|
|
49
|
-
**方式一:从 npm 安装(推荐)**
|
|
50
|
-
|
|
51
|
-
```powershell
|
|
52
|
-
# 1) 先停掉 dsh web(运行中的服务会锁住依赖,装完再起)
|
|
53
|
-
dsh plugin --profile web add "dsh-ssh-tunnel@1.0.1"
|
|
54
|
-
# 2) 重新启动 dsh web
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
包已发布到 npm,随包提供预构建产物,本地不需要构建步骤;换版本就把 `@1.0.1` 换成目标版本。
|
|
58
|
-
|
|
59
|
-
**方式二:从 GitHub 源安装**
|
|
60
|
-
|
|
61
|
-
```powershell
|
|
62
|
-
dsh plugin --profile web add "github:OMSociety/dsh-ssh-tunnel"
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
想复现某次安装就钉住 ref:在仓库地址后加 `#<tag 或提交 sha>`。
|
|
45
|
+
## 安装方式
|
|
66
46
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
```sh
|
|
70
|
-
curl -fsSL https://raw.githubusercontent.com/OMSociety/dsh-ssh-tunnel/main/scripts/install.sh | bash
|
|
71
|
-
```
|
|
47
|
+
**从 npm 安装**
|
|
72
48
|
|
|
73
49
|
```powershell
|
|
74
|
-
|
|
50
|
+
# 先停掉正在运行的 DSH(运行中的服务会锁住依赖,装完再起)
|
|
51
|
+
dsh plugin --profile <profile> add "dsh-ssh-tunnel"
|
|
75
52
|
```
|
|
76
53
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
> **提示**:装好后**刷新一下浏览器页面**,右侧栏才会出现「SSH 隧道」入口——只重启宿主不够,客户端产物是页面加载时取的。
|
|
54
|
+
> **提示**:装好后**刷新一下浏览器页面**,「SSH 隧道」入口才会出现在右侧栏——只重启宿主不够,客户端产物是页面加载时取的。
|
|
80
55
|
|
|
81
56
|
**装完怎么用**
|
|
82
57
|
|
|
@@ -111,54 +86,57 @@ irm https://raw.githubusercontent.com/OMSociety/dsh-ssh-tunnel/main/scripts/inst
|
|
|
111
86
|
| `read_session` / `send_input` / `resize_session` | 交互式会话的读取、输入与窗口尺寸 |
|
|
112
87
|
|
|
113
88
|
- 会话策略:`reuse_or_create`(默认,会复活同主机已断开的会话)、`new`、`require_existing`,或显式传 `session_id`
|
|
114
|
-
-
|
|
89
|
+
- 超时:默认 exec 30s、SFTP 元数据 30s、SFTP 传输 120s、shell 类操作(`read_session` / `send_input` / `resize_session`)10s;`timeout_ms` 可覆盖,取值钳制在 1000–300000,整次调用另有 300s 上限
|
|
90
|
+
- `max_bytes`:exec 与 `sftp_read_text` 的单次输出上限,按字节钳制在 1024–1048576(默认 exec 262144 即 256 KiB、`sftp_read_text` 524288 即 512 KiB);达到上限时结果带 `truncated: true` 且只含前 `max_bytes` 字节
|
|
91
|
+
- exec 结算:结果带 `exitKnown` / `code` / `signal` / `connectionDropped`——正常退出 `exitKnown: true` 且 `code` 为退出码;按信号终止时返回 `signal`;连接被切断时 `code` 为 `null` 且 `connectionDropped: true`
|
|
92
|
+
- 交互式输出的读取走 seq 游标协议(侧栏轮询的 `shellRead`):客户端携带上次读到的 `since`,响应返回 `chunk` / `since` / `seq` / `baseSeq` / `dropped` / `chunkTruncated`;环形缓冲(512 KiB)淘汰后 `baseSeq` 前移且 `dropped: true`,客户端据此全量重取;单次响应的 `chunk` 上限 256 KiB
|
|
115
93
|
- 认证方式只有**密码**或**私钥**;键盘交互式认证(含堡垒机网页 MFA)不支持
|
|
116
94
|
|
|
117
95
|
## 安全
|
|
118
96
|
|
|
97
|
+
**信任边界(客观陈述)**:插件的 HTTP API 挂在 DSH 宿主的本地 web 服务上,只接受本机回环(`localhost` / `127.0.0.1` / `::1`)与配置的可信主机,并校验浏览器 `Origin`;API 没有鉴权 token——**能在本机访问该端口的进程都被视为已授权用户**,可以使用本插件的能力。会话 API 必须带 `projectPathKey`,跨项目不可见。
|
|
98
|
+
|
|
99
|
+
授权与路径守卫动作清单:
|
|
100
|
+
|
|
101
|
+
- **连接必须先授权**:未在「项目授权」里勾选的主机无法建立连接,Connect 不会自动写入授权
|
|
102
|
+
- **授权撤销即时生效**:写类操作在发起前与结果回报前各复检一次授权;撤销后输出被丢弃并以 `not authorized` 失败,下载中的本地文件被删除,受影响的存活会话被关闭,重连拒绝复活已撤销主机
|
|
103
|
+
- **本地路径守卫 fail-closed**:上传 / 下载 / 列举 / 建目录 / 删除 / 重命名的本机路径限制在**项目工作区根**内,词法 **与** realpath 双重校验,越界路径与指向工作区外的符号链接一律拒绝;工作区根取不到时直接拒绝操作(可用环境变量 `DSH_SSH_TUNNEL_WORKSPACE_ROOT` 显式指定)
|
|
119
104
|
- 工具结果与列表 API 不返回 password / PEM / 口令
|
|
120
|
-
- 本地上传、下载、列举、删除的路径限制在**项目工作区根**内(由宿主按当前会话工作区解析,不假设固定挂载点):词法 **与** realpath 双重校验,越界路径与指向工作区外的符号链接一律拒绝
|
|
121
105
|
- 主机密钥以 **SHA256 hex** 存入 `known_hosts.json`;首次连接或指纹变更时在侧栏确认(展示指纹)
|
|
122
|
-
- HTTP API 限制在 loopback / trusted hosts;浏览器 `Origin` 必须匹配;会话 API 必须带 `projectPathKey`
|
|
123
106
|
- 优先使用密钥登录;若 `secrets.json` 可能泄露请立即轮换凭据
|
|
124
|
-
-
|
|
107
|
+
- 认证方式为密码或私钥
|
|
125
108
|
|
|
126
109
|
## 数据放在哪
|
|
127
110
|
|
|
128
|
-
`$DSH_HOME/ssh-tunnel
|
|
111
|
+
`$DSH_HOME/ssh-tunnel/`(目录权限 `0700`;此为 POSIX 系统行为——Windows 的 NTFS 权限由继承 ACL 决定,`chmod` 不改 DACL):
|
|
129
112
|
|
|
130
|
-
| 文件 | 内容 |
|
|
131
|
-
|
|
132
|
-
| `hosts.json` | 主机元数据(不含密钥明文) |
|
|
133
|
-
| `secrets.json` | 密码 / PEM /
|
|
134
|
-
| `grants.json` | `projectPathKey → hostIds[]` |
|
|
135
|
-
| `known_hosts.json` | 已信任的主机密钥指纹 |
|
|
136
|
-
|
|
137
|
-
## 界面国际化
|
|
138
|
-
|
|
139
|
-
- 命名空间:`sshTunnel`;字典 `zh` / `en` 注册到 `ctx.locale`
|
|
140
|
-
- Tab 标题与面板随 DSH 界面语言即时切换
|
|
141
|
-
- 宿主侧 `SSHManager` 的描述保持英文(面向模型)
|
|
113
|
+
| 文件 | 内容 | 权限(POSIX) |
|
|
114
|
+
|---|---|---|
|
|
115
|
+
| `hosts.json` | 主机元数据(不含密钥明文) | `0600` |
|
|
116
|
+
| `secrets.json` | 密码 / PEM / 口令 | `0600` |
|
|
117
|
+
| `grants.json` | `projectPathKey → hostIds[]` | `0600` |
|
|
118
|
+
| `known_hosts.json` | 已信任的主机密钥指纹 | `0600` |
|
|
142
119
|
|
|
143
120
|
## 开发
|
|
144
121
|
|
|
145
|
-
```
|
|
146
|
-
npm test
|
|
147
|
-
npm run check
|
|
148
|
-
|
|
149
|
-
bash scripts/
|
|
122
|
+
```sh
|
|
123
|
+
npm test # 自检脚本全量回归(实际执行 node scripts/smoke-test.mjs)
|
|
124
|
+
npm run check # 语法检查 + 自检
|
|
125
|
+
node scripts/smoke-test.mjs # 直接运行自检(离线设计,不起 SSH、不起 DSH 进程)
|
|
126
|
+
bash scripts/sync-to-dsh.sh --dry-run # 预览 link: 接入命令;去掉 --dry-run 才会改写 profile
|
|
150
127
|
node scripts/portal-probe.mjs # 客户端 Tab 渲染探针(能解析到 react 时生效,否则明确跳过)
|
|
151
128
|
```
|
|
152
129
|
|
|
153
|
-
|
|
130
|
+
项目目录:
|
|
154
131
|
|
|
155
132
|
```text
|
|
156
133
|
lib/index.js 宿主入口:工具注册、/dsh-ssh-tunnel/api 路由、授权与路径守卫、xterm 资产下发
|
|
157
134
|
lib/client.js 客户端 bundle(已入库;dsh plugin add 不做构建)
|
|
158
|
-
lib/session.js 会话生命周期:连接、keepalive
|
|
159
|
-
lib/shared/ 宿主与客户端共用纯函数:path / host-key / host-summary / http-trust / persist /
|
|
135
|
+
lib/session.js 会话生命周期:连接、keepalive、掉线墓碑与自动重连、exec/SFTP 执行
|
|
136
|
+
lib/shared/ 宿主与客户端共用纯函数:path / args / host-key / host-summary / http-trust / persist /
|
|
160
137
|
session-auth / session-policy / shell-buffer / vendor
|
|
161
138
|
scripts/ install.sh · install.ps1 · sync-to-dsh.sh · smoke-test.mjs · portal-probe.mjs
|
|
139
|
+
scripts/lib/ 安装链共享逻辑(.cjs,install.sh 与 install.ps1 共用)
|
|
162
140
|
cordis.patch.yml 包内 bundle patch,CLI 据此写入 dsh.profile.bundles
|
|
163
141
|
```
|
|
164
142
|
|
|
@@ -170,11 +148,10 @@ cordis.patch.yml 包内 bundle patch,CLI 据此写入 dsh.profile.bundl
|
|
|
170
148
|
|
|
171
149
|
- 如果这个插件对你有帮助,欢迎点亮 Star;有问题或建议请提 [Issue](https://github.com/OMSociety/dsh-ssh-tunnel/issues) 或 [Pull Request](https://github.com/OMSociety/dsh-ssh-tunnel/pulls)。
|
|
172
150
|
- 变更记录见 [CHANGELOG](CHANGELOG.md)。
|
|
173
|
-
- [LiveAgent](https://github.com/thirsty5034/LiveAgent)
|
|
174
|
-
- [dsh-better-sidebar](https://github.com/omdsh-dev/DSH-better-sidebar):右侧栏宿主与 Tab 契约
|
|
151
|
+
- LiveAgent([thirsty5034/LiveAgent](https://github.com/thirsty5034/LiveAgent)):产品形态与部分 UX 的参考来源(见上文「参考来源」)
|
|
175
152
|
- [dsh-git-forge](https://github.com/OMSociety/dsh-git-forge):同门插件,Git 凭据与 push 策略
|
|
176
|
-
-
|
|
153
|
+
- DeepSeek Harness:插件、工具与 agent shell 的宿主
|
|
177
154
|
|
|
178
155
|
## 许可证与作者
|
|
179
156
|
|
|
180
|
-
[MIT](LICENSE)
|
|
157
|
+
[MIT](LICENSE)。授权条款与版权归属以 LICENSE 为准;上游项目与代码作者 [@thirsty5034](https://github.com/thirsty5034)。
|
package/README_en.md
CHANGED
|
@@ -7,17 +7,18 @@
|
|
|
7
7
|
<p>The model drives the <strong>SSHManager</strong> tool to run commands and move files; you manage hosts and grants in the sidebar. <strong>Secrets never enter model context</strong>.</p>
|
|
8
8
|
|
|
9
9
|
<p>
|
|
10
|
-
<a href="https://
|
|
11
|
-
<
|
|
10
|
+
<a href="https://www.npmjs.com/package/dsh-ssh-tunnel"><img src="https://img.shields.io/npm/v/dsh-ssh-tunnel?label=version&color=4f6ef7" alt="Version"></a>
|
|
11
|
+
<img src="https://img.shields.io/badge/DSH-%3E%3D0.2.0--rc.2%20%3C0.3.0--0-4f6ef7" alt="DSH">
|
|
12
12
|
<a href="LICENSE"><img src="https://img.shields.io/github/license/OMSociety/dsh-ssh-tunnel?color=4f6ef7" alt="License"></a>
|
|
13
13
|
<a href="https://github.com/OMSociety/dsh-ssh-tunnel/stargazers"><img src="https://img.shields.io/github/stars/OMSociety/dsh-ssh-tunnel?color=4f6ef7" alt="Stars"></a>
|
|
14
14
|
<a href="https://github.com/OMSociety/dsh-ssh-tunnel/issues"><img src="https://img.shields.io/github/issues/OMSociety/dsh-ssh-tunnel?color=4f6ef7" alt="Issues"></a>
|
|
15
15
|
</p>
|
|
16
|
+
<a href="#what-this-is">What this is</a> • <a href="#features">Features</a> • <a href="#installation">Installation</a> • <a href="#sidebar">Sidebar</a> • <a href="#model-tool">Model tool</a> • <a href="#security">Security</a> • <a href="#development">Development</a> • <a href="#license-and-author">License and author</a>
|
|
16
17
|
</div>
|
|
17
18
|
|
|
18
19
|
## What this is
|
|
19
20
|
|
|
20
|
-
**
|
|
21
|
+
**DSH SSH Tunnel** is a community plugin for DeepSeek Harness, mounted as a tab in the **official DSH right sidebar**. It collects your SSH hosts into one **host inventory**, authorizes them per project, and then opens an **interactive terminal** (xterm) or **dual-pane SFTP** in the center panel.
|
|
21
22
|
|
|
22
23
|
It does **not** replace the global `fs` / `subprocess` with a remote disk: remote operations happen only in the `SSHManager` tool you call explicitly and inside the panel, and local and remote files remain two clearly separated sides.
|
|
23
24
|
|
|
@@ -27,8 +28,6 @@ Its companion plugin is [dsh-git-forge](https://github.com/OMSociety/dsh-git-for
|
|
|
27
28
|
|
|
28
29
|
**The product shape and several UX patterns are informed by the open-source [LiveAgent](https://github.com/thirsty5034/LiveAgent)** (multi-host SSH inventory, project-scoped access, sidebar tunnel management, center terminal / SFTP surfaces).
|
|
29
30
|
|
|
30
|
-
This package is a **DSH-native implementation** (Cordis host/client plugin, `dsh-better-sidebar` tab, `SSHManager` tool, DSH-local secret layout). It is **not** a git fork of LiveAgent and does **not** vendor LiveAgent sources. Consult LiveAgent under its own license when comparing designs.
|
|
31
|
-
|
|
32
31
|
## Features
|
|
33
32
|
|
|
34
33
|
| Feature | Description |
|
|
@@ -42,38 +41,15 @@ This package is a **DSH-native implementation** (Cordis host/client plugin, `dsh
|
|
|
42
41
|
| **Local path guard** | Local upload / download / list / delete paths are constrained to the **project workspace root**, with both lexical and realpath checks |
|
|
43
42
|
| **Bilingual UI** | Sidebar and panel follow the DSH interface language between Chinese and English live |
|
|
44
43
|
|
|
45
|
-
##
|
|
46
|
-
|
|
47
|
-
**Option 1: install from npm (recommended)**
|
|
48
|
-
|
|
49
|
-
```powershell
|
|
50
|
-
# 1) stop dsh web first (a running server holds the dependency lock; start it again afterwards)
|
|
51
|
-
dsh plugin --profile web add "dsh-ssh-tunnel@1.0.1"
|
|
52
|
-
# 2) restart dsh web
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
The package is published to npm and ships the prebuilt artifacts, so no local build step is involved. Replace `@1.0.1` to install another version.
|
|
56
|
-
|
|
57
|
-
**Option 2: install from the GitHub source**
|
|
58
|
-
|
|
59
|
-
```powershell
|
|
60
|
-
dsh plugin --profile web add "github:OMSociety/dsh-ssh-tunnel"
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
To reproduce a specific install, pin a ref by appending `#<tag or commit sha>` to the repository URL.
|
|
44
|
+
## Installation
|
|
64
45
|
|
|
65
|
-
**
|
|
66
|
-
|
|
67
|
-
```sh
|
|
68
|
-
curl -fsSL https://raw.githubusercontent.com/OMSociety/dsh-ssh-tunnel/main/scripts/install.sh | bash
|
|
69
|
-
```
|
|
46
|
+
**Install from npm**
|
|
70
47
|
|
|
71
48
|
```powershell
|
|
72
|
-
|
|
49
|
+
# stop the running DSH first (a live service holds the dependency lock; start it again afterwards)
|
|
50
|
+
dsh plugin --profile <profile> add "dsh-ssh-tunnel"
|
|
73
51
|
```
|
|
74
52
|
|
|
75
|
-
The script installs from the GitHub source by default (`bash scripts/install.sh --from npm 1.0.1` switches to npm). Besides installing, it adds this plugin to the profile's `minimumReleaseAgeExclude`, verifies that `dsh.profile.bundles` really received the entry, and removes the mount older versions wrote by hand into the profile's `cordis.patch.yml`. Add `--dry-run` to print the plan without touching anything.
|
|
76
|
-
|
|
77
53
|
> **Note**: After installing, **refresh the browser page** for the "SSH Tunnel" entry to appear in the sidebar — restarting the host alone is not enough, because the client artifact is fetched when the page loads.
|
|
78
54
|
|
|
79
55
|
**First run**
|
|
@@ -109,42 +85,44 @@ One sidebar tab with three pages:
|
|
|
109
85
|
| `read_session` / `send_input` / `resize_session` | Read, feed and resize an interactive session |
|
|
110
86
|
|
|
111
87
|
- Session strategies: `reuse_or_create` (default; revives a disconnected session for that host), `new`, `require_existing`, or an explicit `session_id`
|
|
112
|
-
-
|
|
88
|
+
- Timeouts: exec 30s, SFTP metadata 30s, SFTP transfers 120s, shell operations (`read_session` / `send_input` / `resize_session`) 10s by default; `timeout_ms` overrides them, clamped to 1000–300000, with a 300s ceiling for the whole call
|
|
89
|
+
- `max_bytes`: per-call output cap for exec and `sftp_read_text`, clamped to 1024–1048576 bytes (defaults: 262144 = 256 KiB for exec, 524288 = 512 KiB for `sftp_read_text`); when the cap is hit the result sets `truncated: true` and carries only the first `max_bytes` bytes
|
|
90
|
+
- exec settlement: results carry `exitKnown` / `code` / `signal` / `connectionDropped` — a normal exit sets `exitKnown: true` with the exit code in `code`; a signal death returns `signal`; a cut connection returns `code: null` with `connectionDropped: true`
|
|
91
|
+
- Interactive output is read through a seq cursor protocol (the sidebar polling API `shellRead`): the client sends the last `since` it has seen, and the response returns `chunk` / `since` / `seq` / `baseSeq` / `dropped` / `chunkTruncated`; ring-buffer eviction (512 KiB) moves `baseSeq` forward and sets `dropped: true`, telling the client to refetch in full; a single response caps `chunk` at 256 KiB
|
|
113
92
|
- Authentication is **password** or **private key** only; keyboard-interactive (including bastion web MFA) is not supported
|
|
114
93
|
|
|
115
94
|
## Security
|
|
116
95
|
|
|
96
|
+
**Trust boundary (stated as it is)**: the plugin's HTTP API is served by the DSH host's local web server and only accepts loopback (`localhost` / `127.0.0.1` / `::1`) plus configured trusted hosts, with the browser `Origin` checked; the API has no authentication token — **any local process that can reach the port is treated as an authorized user** and may use the plugin's capabilities. Session APIs require `projectPathKey` and are invisible across projects.
|
|
97
|
+
|
|
98
|
+
Authorization and path-guard action list:
|
|
99
|
+
|
|
100
|
+
- **Grant before connect**: a host that is not checked under Project access cannot be connected, and Connect never auto-writes grants
|
|
101
|
+
- **Revocation takes effect immediately**: write operations re-check the grant once before being issued and once before the result is delivered; after a revocation the output is discarded and the call fails with `not authorized`, a local file mid-download is deleted, affected live sessions are closed, and reconnect refuses to resurrect the revoked host
|
|
102
|
+
- **Local path guard fails closed**: local upload / download / list / mkdir / delete / rename paths are constrained to the **project workspace root**, with both lexical and realpath checks, and out-of-bounds paths or symlinks pointing outside the workspace are rejected; when no workspace root can be resolved the operation is refused (the environment variable `DSH_SSH_TUNNEL_WORKSPACE_ROOT` can set one explicitly)
|
|
117
103
|
- Tool results and list APIs never return password / PEM / passphrase
|
|
118
|
-
- Local upload, download, list and delete paths are constrained to the **project workspace root** (the host resolves it from the current session workspace rather than assuming a fixed mount point): both lexical and realpath checks apply, and out-of-bounds paths or symlinks pointing outside the workspace are rejected
|
|
119
104
|
- Host keys are stored as **SHA256 hex** in `known_hosts.json`; the first connect or a fingerprint change is confirmed in the sidebar with the fingerprint shown
|
|
120
|
-
- The HTTP API is fenced to loopback / trusted hosts; the browser `Origin` must match; session APIs require `projectPathKey`
|
|
121
105
|
- Prefer key-based auth; rotate credentials immediately if `secrets.json` may have leaked
|
|
122
|
-
-
|
|
106
|
+
- Authentication is password or private key
|
|
123
107
|
|
|
124
108
|
## Where data lives
|
|
125
109
|
|
|
126
|
-
Under `$DSH_HOME/ssh-tunnel/` (directory mode `0700`):
|
|
110
|
+
Under `$DSH_HOME/ssh-tunnel/` (directory mode `0700`; this is POSIX behavior — on Windows NTFS permissions come from inherited ACLs and `chmod` does not change the DACL):
|
|
127
111
|
|
|
128
|
-
| File | Contents |
|
|
129
|
-
|
|
130
|
-
| `hosts.json` | Host metadata (no secret material) |
|
|
131
|
-
| `secrets.json` | Passwords / PEM / passphrases
|
|
132
|
-
| `grants.json` | `projectPathKey → hostIds[]` |
|
|
133
|
-
| `known_hosts.json` | Trusted host key fingerprints |
|
|
134
|
-
|
|
135
|
-
## UI internationalization
|
|
136
|
-
|
|
137
|
-
- Namespace: `sshTunnel`; dictionaries `zh` / `en` registered on `ctx.locale`
|
|
138
|
-
- Tab title and panel follow the DSH interface language live
|
|
139
|
-
- Host-side `SSHManager` strings stay English (model-facing)
|
|
112
|
+
| File | Contents | Mode (POSIX) |
|
|
113
|
+
|---|---|---|
|
|
114
|
+
| `hosts.json` | Host metadata (no secret material) | `0600` |
|
|
115
|
+
| `secrets.json` | Passwords / PEM / passphrases | `0600` |
|
|
116
|
+
| `grants.json` | `projectPathKey → hostIds[]` | `0600` |
|
|
117
|
+
| `known_hosts.json` | Trusted host key fingerprints | `0600` |
|
|
140
118
|
|
|
141
119
|
## Development
|
|
142
120
|
|
|
143
|
-
```
|
|
144
|
-
npm test
|
|
145
|
-
npm run check
|
|
146
|
-
|
|
147
|
-
bash scripts/
|
|
121
|
+
```sh
|
|
122
|
+
npm test # full regression of the smoke scripts (runs node scripts/smoke-test.mjs)
|
|
123
|
+
npm run check # syntax check + smoke tests
|
|
124
|
+
node scripts/smoke-test.mjs # run the smoke scripts directly (offline by design: no SSH, no DSH process)
|
|
125
|
+
bash scripts/sync-to-dsh.sh --dry-run # preview the link: registration; drop --dry-run to actually rewrite the profile
|
|
148
126
|
node scripts/portal-probe.mjs # client tab render probe (runs when react resolves, otherwise skips)
|
|
149
127
|
```
|
|
150
128
|
|
|
@@ -153,10 +131,11 @@ Layout and where to change what:
|
|
|
153
131
|
```text
|
|
154
132
|
lib/index.js Host entry: tool registration, /dsh-ssh-tunnel/api routes, grants and path guard, xterm asset serving
|
|
155
133
|
lib/client.js Client bundle (committed; dsh plugin add does not build)
|
|
156
|
-
lib/session.js Session lifecycle: connect, keepalive, drop tombstones and auto-reconnect
|
|
157
|
-
lib/shared/ Pure functions shared by host and client: path / host-key / host-summary / http-trust / persist /
|
|
134
|
+
lib/session.js Session lifecycle: connect, keepalive, drop tombstones and auto-reconnect, exec/SFTP execution
|
|
135
|
+
lib/shared/ Pure functions shared by host and client: path / args / host-key / host-summary / http-trust / persist /
|
|
158
136
|
session-auth / session-policy / shell-buffer / vendor
|
|
159
137
|
scripts/ install.sh · install.ps1 · sync-to-dsh.sh · smoke-test.mjs · portal-probe.mjs
|
|
138
|
+
scripts/lib/ Installer shared logic (.cjs, used by both install.sh and install.ps1)
|
|
160
139
|
cordis.patch.yml In-package bundle patch the CLI turns into dsh.profile.bundles
|
|
161
140
|
```
|
|
162
141
|
|
|
@@ -168,11 +147,10 @@ cordis.patch.yml In-package bundle patch the CLI turns into dsh.profile.b
|
|
|
168
147
|
|
|
169
148
|
- If this plugin helps you, a Star is welcome; questions and suggestions go to [Issues](https://github.com/OMSociety/dsh-ssh-tunnel/issues) or [Pull Requests](https://github.com/OMSociety/dsh-ssh-tunnel/pulls).
|
|
170
149
|
- Changes are recorded in the [CHANGELOG](CHANGELOG.md).
|
|
171
|
-
- [LiveAgent](https://github.com/thirsty5034/LiveAgent): prior art for the product shape and several UX patterns (see "Prior art" above)
|
|
172
|
-
- [dsh-better-sidebar](https://github.com/omdsh-dev/DSH-better-sidebar): the sidebar host and tab contract
|
|
150
|
+
- LiveAgent ([thirsty5034/LiveAgent](https://github.com/thirsty5034/LiveAgent)): prior art for the product shape and several UX patterns (see "Prior art" above)
|
|
173
151
|
- [dsh-git-forge](https://github.com/OMSociety/dsh-git-forge): sibling plugin for Git credentials and push policy
|
|
174
|
-
-
|
|
152
|
+
- DeepSeek Harness: the host for plugins, tools and agent shells
|
|
175
153
|
|
|
176
154
|
## License and author
|
|
177
155
|
|
|
178
|
-
[MIT](LICENSE).
|
|
156
|
+
[MIT](LICENSE). The license terms and copyright are defined by LICENSE; upstream project and code author [@thirsty5034](https://github.com/thirsty5034).
|
package/cordis.patch.yml
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# dsh-ssh-tunnel bundle mount
|
|
2
|
-
- insert:
|
|
3
|
-
- id: ssh-tunnel
|
|
4
|
-
name: dsh-ssh-tunnel
|
|
1
|
+
# dsh-ssh-tunnel bundle mount
|
|
2
|
+
- insert:
|
|
3
|
+
- id: ssh-tunnel
|
|
4
|
+
name: dsh-ssh-tunnel
|