@mrrisega/dsh-remote 0.6.18-beta.4 → 0.6.18-beta.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrrisega/dsh-remote",
3
- "version": "0.6.18-beta.4",
3
+ "version": "0.6.18-beta.5",
4
4
  "description": "手机远程控制 DeepSeek Harness · Remote control DeepSeek Harness (dsh web) from any phone browser — 100% 全功能 App 级体验:发消息、看工具执行、审批权限、改设置、管凭据,含特权操作,免内网穿透。一条命令安装 npx @mrrisega/dsh-remote。Mobile remote control for DSH, self-host or SaaS, no server needed on LAN.",
5
5
  "keywords": [
6
6
  "deepseek-harness",
@@ -5710,6 +5710,31 @@ async function sendWeChatProxy(relayDir, res, routePath, init) {
5710
5710
  // DSH_REMOTE_TRUSTED_HOSTS=a.com,b(放在 dsh web 前面的反向代理需要它)。
5711
5711
  // ============================================================
5712
5712
  const LOOPBACK_HOST_RE = /^(127\.0\.0\.1|localhost|\[::1\]|::1)(:\d+)?$/i;
5713
+ /**
5714
+ * 私网 / 链路本地 / mDNS 的 authority。
5715
+ *
5716
+ * 为什么必须放行:本插件**自己**会把 dsh web 绑到 `0.0.0.0`(见 `cordis.patch.yml` 里那段
5717
+ * `remote-web-ui lan-bind block`)——「局域网直连」是产品功能(同一 Wi-Fi 下的平板/手机直连),
5718
+ * 只认 loopback 会把它整个挡死。真机验收时就是这么发现的。
5719
+ *
5720
+ * 放行它们**不等于**放开安全边界:
5721
+ * · DNS rebinding 仍被挡:攻击者页面把 evil.com 指到本机时,请求头里的 Host 是 **evil.com**
5722
+ * (浏览器按 URL 里的主机名发 Host,不看它解析到哪个 IP)→ 仍然命中 bad-host;
5723
+ * · CSRF 仍被挡:跨站请求的 Origin 与 Host 必然不同源 → 命中 origin-mismatch(下面 ②);
5724
+ * · 唯一多让出来的是「Host 恰好是私网 IP 字面量」的请求,而它只可能来自**同源页面**
5725
+ * (也就是本机自己服务的那个页面),不构成新的攻击面。
5726
+ */
5727
+ const PRIVATE_HOST_RE = new RegExp(
5728
+ "^(?:" + [
5729
+ "10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}",
5730
+ "192\\.168\\.\\d{1,3}\\.\\d{1,3}",
5731
+ "172\\.(?:1[6-9]|2\\d|3[01])\\.\\d{1,3}\\.\\d{1,3}",
5732
+ "169\\.254\\.\\d{1,3}\\.\\d{1,3}",
5733
+ "[a-z0-9][a-z0-9-]*\\.local",
5734
+ "\\[(?:fe80|fc|fd)[0-9a-f:]*\\]"
5735
+ ].join("|") + ")(:\\d+)?$",
5736
+ "i"
5737
+ );
5713
5738
  const LOCAL_GUARD_LOGGED = new Map(); // reason -> 上次告警时间(同一原因 60s 只报一次,避免刷日志)
5714
5739
 
5715
5740
  /** 只读地判断一个请求是否来自"本机同源页面"。返回 null=放行,否则返回拒绝原因。
@@ -5721,7 +5746,7 @@ export function localRequestBlocked(req) {
5721
5746
  const headers = (req && req.headers) || {};
5722
5747
  const host = String(headers.host || "").trim().toLowerCase();
5723
5748
  if (!host) return "missing-host";
5724
- if (!LOOPBACK_HOST_RE.test(host) && !extra.includes(host)) return "bad-host";
5749
+ if (!LOOPBACK_HOST_RE.test(host) && !PRIVATE_HOST_RE.test(host) && !extra.includes(host)) return "bad-host";
5725
5750
  const origin = String(headers.origin || "").trim().toLowerCase();
5726
5751
  // Origin: null(file:// 或 sandbox iframe)不在这里判,交给下面的 sec-fetch-site
5727
5752
  if (origin && origin !== "null") {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-remote-web",
3
- "version": "0.6.18-beta.4",
3
+ "version": "0.6.18-beta.5",
4
4
  "description": "公网远程控制 DeepSeek Harness(dsh web):安装即得专属加密地址,人在外面也能用手机访问电脑上的 dsh——无需同一局域网/WiFi、无需公网 IP 与内网穿透,全程加密;手机端 100% 还原电脑体验(对话/工具/审批/设置)。技术用户可选自建服务,流量走自己的服务器。Remote control DeepSeek Harness (dsh web) from anywhere over the public internet — install, get an encrypted URL, use it from your phone on any network (dual-half cordis plugin: Settings panel + same-origin /dsh-remote routes). (2026-09 由 dsh-remote-ui 更名 / renamed from dsh-remote-ui; dsh-remote 的 dsh web 插件半,不是纯 UI/皮肤插件)",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -31,11 +31,26 @@ test("守卫放行:手机隧道形态(Host=loopback、无 Origin、无 sec-f
31
31
  assert.equal(localRequestBlocked(req({ host: "127.0.0.1:3080", "sec-fetch-site": "same-origin" })), null);
32
32
  });
33
33
 
34
+ test("守卫放行:局域网直连(插件自己 lan-bind 到 0.0.0.0,这是产品功能,不能一刀切挡掉)", () => {
35
+ // 2026-09-27 真机验收时发现:只认 loopback 会把「同一 Wi-Fi 下直连」整个挡死
36
+ for (const host of ["172.16.27.98:3080", "192.168.1.30:3080", "10.0.0.5:3080", "mac.local:3080"]) {
37
+ assert.equal(localRequestBlocked(req({ host })), null, `${host} 是私网/mDNS,应放行`);
38
+ }
39
+ // 但打这些地址的**跨站**请求仍必须拒(CSRF 与 Host 白名单是两条独立判据)
40
+ assert.equal(
41
+ localRequestBlocked(req({ host: "172.16.27.98:3080", origin: "http://evil.com" })),
42
+ "origin-mismatch",
43
+ "局域网地址上的跨站 POST 同样要挡"
44
+ );
45
+ });
46
+
34
47
  test("DNS rebinding 必拒:Host 不是 loopback(即使 Origin 与该 Host 自洽)", () => {
35
48
  // rebinding 的形态:页面来源与目标都是 evil.com,Origin/Host 互相自洽 —— 只有 Host 白名单能挡
36
49
  assert.equal(localRequestBlocked(req({ host: "evil.com", origin: "http://evil.com" })), "bad-host");
37
50
  assert.equal(localRequestBlocked(req({ host: "evil.com:3080" })), "bad-host");
38
51
  assert.equal(localRequestBlocked(req({ host: "127.0.0.1.evil.com" })), "bad-host");
52
+ assert.equal(localRequestBlocked(req({ host: "172.16.27.98.evil.com" })), "bad-host", "私网前缀伪装也要拒");
53
+ assert.equal(localRequestBlocked(req({ host: "8.8.8.8:3080" })), "bad-host", "公网 IP 字面量不在白名单");
39
54
  assert.equal(localRequestBlocked(req({})), "missing-host");
40
55
  });
41
56