@zhangfengshun/dsh-remote-ssh 2.3.6 → 2.3.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 CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  本文件的版本号与 `package.json` 的 `version` 保持一致。每个版本对应一个 Cordis Package 快照(`pkg-N`)。
4
4
 
5
+ ## [2.3.7] — Windows host 连接诊断增强:过滤 PQ 警告、ssh -v 诊断、跨平台测试探测
6
+ ### 修复
7
+ - **过滤 OpenSSH「后量子 KEX」警告横幅**:新版本客户端对非 PQ 加密的 stderr 警告(三行 `** WARNING…`)与认证成败无关,此前混入错误信息会误导用户以为是不支持加密算法;现在统一过滤(错误提示、一次性连接 stderr、持久会话关闭诊断)。
8
+ - **测试连接探测命令改为跨平台 `echo __DSH_OK__`**:旧命令尾部 `uname -a`/`pwd` 在 Windows host(cmd/PowerShell)上必然报错,导致**认证成功的 Windows host 也误报失败**。
9
+ - **认证失败自动带 ssh -v 诊断**:测试连接认证失败时重跑一次 `ssh -v` 握手,抽取关键诊断行(私钥加载 / 公钥提供 / 服务器拒绝方法 / 口令要求)附在错误信息里,用户一眼定位失败环节。
10
+ - **公钥失败提示覆盖 Windows 三大圈套**:① 私钥带口令 + 批处理模式无法交互输入(先 ssh-add 或去口令);② Windows host 且用户在 Administrators 组时公钥须写入 `C:\ProgramData\ssh\administrators_authorized_keys`;③ 用户名写法(`user` / `.\user` / `user@domain`)须与手动连接一致。
11
+
5
12
  ## [2.3.6] — settings 注册迁移至字符串命名空间,移除 legacy 导出依赖
6
13
  ### 变更
7
14
  - **不再从 `@deepseek-ai/dsh-settings` 导入 `settingsNamespace`**:该导出在 dsh-settings 中被标注为 legacy(仅为 alpha.2 之前的插件保留,未来版本可能移除)。`parseSettingsNamespace` 实为恒等函数(校验 `/^[a-z][a-z0-9-]*$/` 后原样返回),故 `register(NS, schema)` 与原 `register(settingsNamespace(NS), schema)` 存储键完全一致——零数据迁移,老用户配置无缝保留。
package/lib/index.js CHANGED
@@ -216,7 +216,7 @@ class CommandSession {
216
216
  });
217
217
  const onEnd = () => {
218
218
  this.alive = false;
219
- const errMsg = "ssh 会话已关闭" + (this.errBuf.trim() ? ": " + this.errBuf.trim().slice(-300) : "");
219
+ const errMsg = "ssh 会话已关闭" + (this.errBuf.trim() ? ": " + stripPqBanner(this.errBuf).trim().slice(-300) : "");
220
220
  if (this.current) { this.current.reject(new Error(errMsg)); this.current = null; }
221
221
  while (this.queue.length) this.queue.shift().reject(new Error(errMsg));
222
222
  };
@@ -336,7 +336,7 @@ class CommandSession {
336
336
  }
337
337
  }
338
338
 
339
- function sshArgv(p, remoteCmd, tty) {
339
+ function sshArgv(p, remoteCmd, tty, extraOpts) {
340
340
  const opts = ["ssh"];
341
341
  if (tty) opts.push("-tt");
342
342
  opts.push("-p", String(p.port || 22));
@@ -356,6 +356,7 @@ function sshArgv(p, remoteCmd, tty) {
356
356
  opts.push("-o", "BatchMode=yes");
357
357
  opts.push("-o", "PreferredAuthentications=publickey");
358
358
  }
359
+ if (Array.isArray(extraOpts) && extraOpts.length) opts.push(...extraOpts);
359
360
  const target = String(p.user || "") + "@" + String(p.host || "");
360
361
  const head = (p.authMethod === "password" && p.password) ? ["sshpass", "-p", String(p.password)] : [];
361
362
  const argv = head.concat(opts, [target]);
@@ -363,12 +364,23 @@ function sshArgv(p, remoteCmd, tty) {
363
364
  return argv;
364
365
  }
365
366
 
366
- async function runRemote(subprocess, p, remoteCmd, stdinData, maxBytes) {
367
+ /** 过滤 OpenSSH 客户端的“后量子 KEX”警告横幅(stderr 噪音,与认证成败无关,
368
+ * 混进错误信息会误导用户以为是不支持加密算法)。 */
369
+ function stripPqBanner(text) {
370
+ return String(text || "")
371
+ .split("\n")
372
+ .filter(function (line) {
373
+ return !/^\s*\*\*\s*(WARNING: connection is not using a post-quantum|This session may be vulnerable to .?store now|The server may need to be upgraded)/i.test(line);
374
+ })
375
+ .join("\n");
376
+ }
377
+
378
+ async function runRemote(subprocess, p, remoteCmd, stdinData, maxBytes, extraOpts) {
367
379
  const max = maxBytes || MAX_BYTES;
368
380
  let handle;
369
381
  try {
370
382
  handle = subprocess.spawn({
371
- argv: sshArgv(p, remoteCmd, false),
383
+ argv: sshArgv(p, remoteCmd, false, extraOpts),
372
384
  cwd: process.cwd(),
373
385
  stdio: {
374
386
  stdin: stdinData !== undefined ? { data: String(stdinData) } : "ignore",
@@ -389,7 +401,7 @@ async function runRemote(subprocess, p, remoteCmd, stdinData, maxBytes) {
389
401
  const so = (handle.collected && handle.collected.stdout) ? handle.collected.stdout.readFrom(0) : { text: "", nextOffset: 0, lossy: false };
390
402
  const se = (handle.collected && handle.collected.stderr) ? handle.collected.stderr.readFrom(0) : { text: "", nextOffset: 0, lossy: false };
391
403
  const outText = so.text;
392
- const errText = se.text;
404
+ const errText = stripPqBanner(se.text);
393
405
  let error = "";
394
406
  if (outcome.exitCode !== 0 && !outText && !errText) {
395
407
  error = "ssh 退出码 " + outcome.exitCode + (outcome.signal ? " (signal " + outcome.signal + ")" : "");
@@ -1054,12 +1066,12 @@ function apply(ctx, config) {
1054
1066
  }
1055
1067
  /** 把 SSH 常见失败翻译成带修复建议的中文提示。 */
1056
1068
  function sshErrorHint(text) {
1057
- const t = String(text || "");
1069
+ const t = stripPqBanner(String(text || ""));
1058
1070
  if (/remote port forwarding failed for listen port (\d+)/i.test(t)) {
1059
1071
  const m = t.match(/remote port forwarding failed for listen port (\d+)/i);
1060
1072
  return "SSH 端口转发失败(本地端口 " + m[1] + " 已被占用)。这通常由 ~/.ssh/config 里的 RemoteForward 造成:远端该端口被上次会话占用时,ExitOnForwardFailure yes 会让 ssh 直接退出。本插件已对该连接加 ClearAllForwardings,若仍出现请检查 ssh config。原始信息: " + t.trim().slice(0, 300);
1061
1073
  }
1062
- if (/permission denied \(publickey/i.test(t)) return "公钥认证失败:请检查 keyPath 私钥路径是否正确、远端 ~/.ssh/authorized_keys 是否包含对应公钥。原始信息: " + t.trim().slice(0, 300);
1074
+ if (/permission denied \(publickey/i.test(t)) return "公钥认证失败(服务器拒绝了公钥)。请依次检查:① keyPath 指向的私钥是否正确、是否带口令——测试连接以批处理模式运行无法交互输口令,可先 ssh-add 加载或改用无口令密钥;② 远端是否收录对应公钥——Windows 主机且目标用户在 Administrators 组时需写入 C:\\ProgramData\\ssh\\administrators_authorized_keys;③ profile 的用户名写法与手动连接是否一致(user / .\\user / user@domain 在 Windows sshd 中解析不同)。原始信息: " + t.trim().slice(0, 300);
1063
1075
  if (/connection refused/i.test(t)) return "连接被拒绝:请确认远端 sshd 已启动且端口正确。原始信息: " + t.trim().slice(0, 300);
1064
1076
  if (/connection timed out|timed out/i.test(t)) return "连接超时:请确认网络可达、端口开放,或检查 ProxyJump 配置。原始信息: " + t.trim().slice(0, 300);
1065
1077
  if (/could not resolve hostname/i.test(t)) return "无法解析主机名:请检查连接配置中的 host 拼写。原始信息: " + t.trim().slice(0, 300);
@@ -1239,7 +1251,26 @@ function apply(ctx, config) {
1239
1251
  testConnection: async (args) => {
1240
1252
  const p = getProfile(args && args.id);
1241
1253
  if (!p) return { ok: false, error: "未找到连接配置" };
1242
- return await runRemote(subprocess, p, "echo __DSH_OK__; hostname; whoami; pwd; uname -a", undefined, 256 * 1024);
1254
+ // 认证探测只用跨平台 echo:Windows host uname/pwd,且默认 shell 语义各异
1255
+ // (旧命令尾部 uname -a 会让认证成功的 Windows host 也误报失败)。
1256
+ const r = await runRemote(subprocess, p, "echo __DSH_OK__", undefined, 16 * 1024);
1257
+ if (r.ok) return r;
1258
+ const baseErr = sshErrorHint([r.error, stripPqBanner(r.stderr).trim()].filter(Boolean).join(" ") || ("ssh 退出码 " + r.exitCode));
1259
+ // 认证类失败:带 ssh -v 重跑一次握手 + 认证,抽取关键诊断行
1260
+ // (私钥加载 / 公钥提供 / 服务器拒绝方法),定位 Windows 常见坑。
1261
+ let error = baseErr;
1262
+ if (/permission denied|publickey/i.test(baseErr + " " + stripPqBanner(r.stderr))) {
1263
+ const diag = await runRemote(subprocess, p, "exit 0", undefined, 16 * 1024, ["-v"]);
1264
+ const diagText = String(diag.stderr || "")
1265
+ .split("\n")
1266
+ .filter(function (l) {
1267
+ return /debug1: (Offering public key|Authentications that can continue|Server accepts key|Trying private key|no such identity)|Load key|Permission denied|passphrase/i.test(l);
1268
+ })
1269
+ .join("\n")
1270
+ .slice(0, 1200);
1271
+ if (diagText) error = baseErr + "\n\nssh -v 诊断:\n" + diagText;
1272
+ }
1273
+ return { ok: false, exitCode: r.exitCode, signal: r.signal, stdout: r.stdout, stderr: r.stderr, truncated: r.truncated, error: error };
1243
1274
  },
1244
1275
  listWorkspaces: async () => ({ ok: true, workspaces: settingsFace.read().workspaces }),
1245
1276
  createRemoteWorkspace: async (args) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhangfengshun/dsh-remote-ssh",
3
- "version": "2.3.6",
3
+ "version": "2.3.7",
4
4
  "description": "DSH web plugin: VSCode Remote-SSH-like remote development (SSH to supercomputers/servers, remote workspace, file explorer, integrated terminal), integrated with dsh-better-sidebar and DSH settings.",
5
5
  "keywords": [
6
6
  "dsh",