dsh-zen-remote 2.3.1 → 2.3.2

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.md CHANGED
@@ -94,7 +94,7 @@ dsh plugin --profile web add dsh-zen-remote
94
94
  // ~/.dsh/profiles/desktop/package.json
95
95
  {
96
96
  "dependencies": {
97
- "dsh-zen-remote": "^2.3.1" // for local development: "link:/path/to/dsh-zen-remote"
97
+ "dsh-zen-remote": "^2.3.2" // for local development: "link:/path/to/dsh-zen-remote"
98
98
  }
99
99
  }
100
100
  ```
package/README.zh-CN.md CHANGED
@@ -94,7 +94,7 @@ dsh plugin --profile web add dsh-zen-remote
94
94
  // ~/.dsh/profiles/desktop/package.json
95
95
  {
96
96
  "dependencies": {
97
- "dsh-zen-remote": "^2.3.1" // 本地开发换成 "link:/path/to/dsh-zen-remote"
97
+ "dsh-zen-remote": "^2.3.2" // 本地开发换成 "link:/path/to/dsh-zen-remote"
98
98
  }
99
99
  }
100
100
  ```
package/lib/client.js CHANGED
@@ -18886,8 +18886,8 @@ function GatewayWizard(props) {
18886
18886
  };
18887
18887
  if (path === 'lan') {
18888
18888
  // LAN / Tailscale direct connections arrive on the machine's LAN or
18889
- // Tailscale interface, so the gateway child must bind to `0.0.0.0`
18890
- // rather than loopback-only `127.0.0.1`.
18889
+ // Tailscale interface, so the gateway child binds to `0.0.0.0` (with
18890
+ // lan-gate's private-IP wall blocking any unproxied public IP).
18891
18891
  await props.saveRowFields({
18892
18892
  ...baseFields,
18893
18893
  host: '0.0.0.0',
@@ -18903,8 +18903,12 @@ function GatewayWizard(props) {
18903
18903
  });
18904
18904
  }
18905
18905
  else {
18906
+ // Managed Caddy and Cloudflare Tunnel both run on the same host and
18907
+ // connect to the gateway over loopback, so reset `host` back to
18908
+ // `127.0.0.1` in case the user previously enabled LAN mode.
18906
18909
  await props.saveRowFields({
18907
18910
  ...baseFields,
18911
+ host: '127.0.0.1',
18908
18912
  ingressMode: path,
18909
18913
  ingressDomain: domain.trim(),
18910
18914
  ingressHttpsPort: readiness.httpsPort,
@@ -239,6 +239,22 @@ var lastWebClaim = null // { id, secure, at } | null
239
239
  // ---- client identity --------------------------------------------------------
240
240
  function normalizeIp(raw) { return String(raw || '').replace(/^::ffff:/, '') }
241
241
  function isLoopbackIp(ip) { return ip === '127.0.0.1' || ip === '::1' }
242
+ function isPrivateOrLoopbackIp(ip) {
243
+ if (isLoopbackIp(ip)) return true
244
+ var m4 = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(ip)
245
+ if (m4) {
246
+ var a = Number(m4[1]), b = Number(m4[2]), c = Number(m4[3]), d = Number(m4[4])
247
+ if (a > 255 || b > 255 || c > 255 || d > 255) return false
248
+ if (a === 10 || a === 127) return true
249
+ if (a === 172 && b >= 16 && b <= 31) return true
250
+ if (a === 192 && b === 168) return true
251
+ if (a === 169 && b === 254) return true
252
+ if (a === 100 && b >= 64 && b <= 127) return true // CGNAT / Tailscale
253
+ return false
254
+ }
255
+ var lower = String(ip || '').toLowerCase()
256
+ return /^f[cd][0-9a-f]{2}:/.test(lower) || /^fe[89ab][0-9a-f]:/.test(lower)
257
+ }
242
258
  function hasForwardHeaders(req) { return req.headers['x-forwarded-for'] !== undefined || req.headers['x-forwarded-proto'] !== undefined || req.headers['x-forwarded-host'] !== undefined }
243
259
 
244
260
  // Resolve who is really talking to us. Forwarded headers are only trusted
@@ -1186,6 +1202,15 @@ function localOnlyBlocked(req, res) {
1186
1202
 
1187
1203
  var server = http.createServer(function (req, res) {
1188
1204
  var client = resolveClient(req)
1205
+ // Direct (non-proxied) connections from a public IP are refused outright:
1206
+ // plain HTTP on 0.0.0.0:3088 is for loopback, LAN (RFC1918), and Tailscale
1207
+ // (100.64.0.0/10) only — public internet traffic must enter through a
1208
+ // TLS-terminating reverse proxy or tunnel (which connects from loopback or
1209
+ // a configured trustedProxy).
1210
+ if (!client.viaProxy && !isPrivateOrLoopbackIp(client.ip)) {
1211
+ json(req, res, 403, { ok: false, reason: 'public-direct-forbidden' })
1212
+ return
1213
+ }
1189
1214
  var pathname = String(req.url || '/').split('?')[0]
1190
1215
  var local = isLocalDirect(req, client)
1191
1216
  var device = local ? undefined : deviceForReq(req)
@@ -1277,6 +1302,10 @@ var server = http.createServer(function (req, res) {
1277
1302
 
1278
1303
  server.on('upgrade', function (req, socket, head) {
1279
1304
  var client = resolveClient(req)
1305
+ if (!client.viaProxy && !isPrivateOrLoopbackIp(client.ip)) {
1306
+ try { socket.end('HTTP/1.1 403 Forbidden\r\nConnection: close\r\n\r\n') } catch (e) {}
1307
+ return
1308
+ }
1280
1309
  var local = isLocalDirect(req, client)
1281
1310
  var device = local ? undefined : deviceForReq(req)
1282
1311
  if (!local && !device && overRate(client.ip)) { try { socket.end('HTTP/1.1 429 Too Many Requests\r\nConnection: close\r\n\r\n') } catch (e) {} return }
@@ -1 +1 @@
1
- {"version":3,"file":"GatewayWizard.d.ts","sourceRoot":"","sources":["../../../../src/client/wizard/GatewayWizard.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACtC,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAA;AACjF,OAAO,KAAK,EAMV,qBAAqB,EACtB,MAAM,oCAAoC,CAAA;AAmD3C,OAAO,EAAE,EAAE,EAAE,MAAM,eAAe,CAAA;AAMlC,MAAM,WAAW,kBAAmB,SAAQ,YAAY,CAAC,uBAAuB,CAAC,EAAE,WAAW,CAAC,OAAO,EAAE,CAAC;IACvG,qEAAqE;IACrE,MAAM,EAAE,qBAAqB,CAAA;IAC7B,2EAA2E;IAC3E,aAAa,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAC3G;AAoGD,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,SAAS,CA++DlE"}
1
+ {"version":3,"file":"GatewayWizard.d.ts","sourceRoot":"","sources":["../../../../src/client/wizard/GatewayWizard.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACtC,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAA;AACjF,OAAO,KAAK,EAMV,qBAAqB,EACtB,MAAM,oCAAoC,CAAA;AAmD3C,OAAO,EAAE,EAAE,EAAE,MAAM,eAAe,CAAA;AAMlC,MAAM,WAAW,kBAAmB,SAAQ,YAAY,CAAC,uBAAuB,CAAC,EAAE,WAAW,CAAC,OAAO,EAAE,CAAC;IACvG,qEAAqE;IACrE,MAAM,EAAE,qBAAqB,CAAA;IAC7B,2EAA2E;IAC3E,aAAa,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAC3G;AAoGD,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,SAAS,CAm/DlE"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-zen-remote",
3
- "version": "2.3.1",
3
+ "version": "2.3.2",
4
4
  "description": "一个插件、两种角色:主服务端把 DeepSeek Harness 变成手机能安全访问的 PWA(移动端界面、配对码网关、Web Push),并把选定会话共享给已配对的桌面端;子客户端在另一台 DSH 桌面端的侧边栏里远程使用这些会话。One DSH plugin, two roles: the host turns DeepSeek Harness into a phone-reachable PWA (mobile UI, pairing-code gateway behind your own reverse proxy, Web Push) and shares chosen sessions to paired desktops; the client uses those sessions remotely from another DSH desktop app's sidebar.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -600,8 +600,8 @@ export function GatewayWizard(props: GatewayWizardProps): ReactNode {
600
600
  }
601
601
  if (path === 'lan') {
602
602
  // LAN / Tailscale direct connections arrive on the machine's LAN or
603
- // Tailscale interface, so the gateway child must bind to `0.0.0.0`
604
- // rather than loopback-only `127.0.0.1`.
603
+ // Tailscale interface, so the gateway child binds to `0.0.0.0` (with
604
+ // lan-gate's private-IP wall blocking any unproxied public IP).
605
605
  await props.saveRowFields(
606
606
  {
607
607
  ...baseFields,
@@ -618,8 +618,12 @@ export function GatewayWizard(props: GatewayWizardProps): ReactNode {
618
618
  ingressPublicUrl: readiness.publicUrl,
619
619
  })
620
620
  } else {
621
+ // Managed Caddy and Cloudflare Tunnel both run on the same host and
622
+ // connect to the gateway over loopback, so reset `host` back to
623
+ // `127.0.0.1` in case the user previously enabled LAN mode.
621
624
  await props.saveRowFields({
622
625
  ...baseFields,
626
+ host: '127.0.0.1',
623
627
  ingressMode: path,
624
628
  ingressDomain: domain.trim(),
625
629
  ingressHttpsPort: readiness.httpsPort,