@yezack/dsh-remote-mobile 1.10.0 → 1.10.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_EN.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  # dsh-remote-mobile
8
8
 
9
- **DeepSeek Harness (DSH) Remote & Mobile Security Guard Plugin**
9
+ **DeepSeek Harness (DSH) Remote & Mobile Secure Access Proxy Plugin**
10
10
 
11
11
  [![npm version](https://img.shields.io/npm/v/dsh-remote-mobile.svg?style=flat-square&color=3b82f6)](https://www.npmjs.com/package/dsh-remote-mobile)
12
12
  [![npm downloads](https://img.shields.io/npm/dm/dsh-remote-mobile.svg?style=flat-square&color=22c55e)](https://www.npmjs.com/package/dsh-remote-mobile)
@@ -16,7 +16,7 @@
16
16
  [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://github.com/IceApriler/dsh-remote-mobile/pulls)
17
17
 
18
18
  <p align="center">
19
- <b>Zero Core Modifications · Secure LAN / Tailscale Access · QR Pairing · Mobile UI Adaptation · Workspace Parity · Real-Time Push</b>
19
+ <b>No DSH Source Changes · Secure LAN / Tailscale Access · QR Pairing · Mobile UI Adaptation · Workspace Parity · Real-Time Push</b>
20
20
  </p>
21
21
 
22
22
  <p align="center">
@@ -31,6 +31,7 @@ English Documentation · [简体中文](./README.md)
31
31
  <a href="#about">About</a> •
32
32
  <a href="#advantages">Key Advantages</a> •
33
33
  <a href="#quick-start">Quick Start</a> •
34
+ <a href="#modes">Access Modes</a> •
34
35
  <a href="#config">Configuration</a> •
35
36
  <a href="#preview">UI Preview</a> •
36
37
  <a href="#features">Features</a> •
@@ -49,7 +50,7 @@ English Documentation · [简体中文](./README.md)
49
50
 
50
51
  `dsh-remote-mobile` is a dedicated remote and mobile security governance plugin designed specifically for **DeepSeek Harness (DSH)**.
51
52
 
52
- By default, DSH strictly listens only to the local loopback address (`127.0.0.1`), preventing mobile devices, tablets, or external computers from accessing the Web console. This plugin uses **access control middleware** and **request isolation** to securely open up **Tailscale Private Network** and **Local Area Network (LAN / Wi-Fi)** access, complete with RSA transmission encryption, QR code pairing, persistent password auth, and automated brute-force defense.
53
+ By default, DSH strictly listens only to the local loopback address (`127.0.0.1`), preventing mobile devices, tablets, or external computers from accessing the Web console. This plugin uses **access control middleware** and **request isolation** to securely open up **Tailscale Private Network** and **Local Area Network (LAN / Wi-Fi)** access, complete with RSA-encrypted login credentials, QR code pairing, persistent password auth, and automated brute-force defense. The plugin does not provide full TLS; use Tailscale or an outer HTTPS proxy for complete transport encryption.
53
54
 
54
55
  When accessed from a phone, the plugin also adapts the DSH interface for **mobile styles**: a collapsible sidebar drawer with a draggable floating toggle, compatibility with the official 3-column Grid layout & right sidebar, a centered/scaled settings dialog, and dense conversation typography — so the mobile experience feels closer to the desktop.
55
56
 
@@ -60,9 +61,9 @@ When accessed from a phone, the plugin also adapts the DSH interface for **mobil
60
61
 
61
62
  * 🚀 **Break Network Barriers with Desktop-Grade Workspace**: For accessing DSH Web over LAN / Tailscale. On mobile devices, **common desktop operations such as creating / switching workspaces and running terminal commands are supported**.
62
63
  * 📱 **Dedicated Mobile Adaptations & Same-Origin Reuse**: Directly reuses the official DSH Web frontend (the very same UI as desktop) and the wider plugin ecosystem (sidebars, task boards, terminal panels, etc.) without maintaining a separate mobile backend. Includes out-of-the-box mobile adaptations (sidebar drawer toggle, centered settings modal, and compact chat font scaling for higher information density), alongside a built-in **Style Snippet Override system** to toggle presets or inject custom CSS snippets on demand.
63
- * 🛡️ **Security Gateway & Transport Encryption**: Built-in client-side RSA asymmetric public key encryption, `scrypt` salted slow-hashing password persistence, and automated IP lockout upon consecutive brute-force failures, blocking unauthorized access over public/LAN networks.
64
- * 📲 **Quick QR Pairing**: Auto-detects Tailscale CGNAT and LAN IP addresses to generate dedicated pairing QR codes. Scan the code to authenticate and obtain persistent credentials.
65
- * 🔄 **SSE Real-Time Push**: Device connection, reconnection, revocation, and security alert events are pushed in real time via Server-Sent Events without polling.
64
+ * 🛡️ **Secure Access Proxy & Credential Encryption**: Login credentials use client-side RSA asymmetric encryption, `scrypt` salted slow-hashing password persistence, and automated IP lockout upon consecutive brute-force failures, blocking unauthorized access over public/LAN networks.
65
+ * 📲 **Quick QR Pairing**: Auto-detects Tailscale CGNAT and LAN IP addresses to generate dedicated pairing QR codes. Scan to open a pre-filled login page; confirm to complete authorization.
66
+ * 🔄 **SSE Real-Time Push**: Device connection, reconnection, revocation, and IP-lock events are pushed via SSE; the settings page retains low-frequency polling as a fallback.
66
67
  * 🤝 **Generic Plugin Coexistence Protection**: Starts safely alongside other remote/Web access plugins by automatically yielding the shared service; the settings panel shows a conflict banner with a one-click copyable diagnosis & fix report.
67
68
 
68
69
  ---
@@ -84,30 +85,45 @@ dsh plugin --profile web add dsh-remote-mobile
84
85
  ---
85
86
 
86
87
  <span id="config"></span>
87
- ### 2. Configuration (Zero-Config for New Installs; Migration for Existing Users)
88
+ ### 2. Configuration (Plugin 1.10.0+ Requires No webserver Config)
88
89
 
89
- > [!TIP]
90
- > **🚀 DSH 0.2.1+ Architecture Update (Zero Configuration)**:
91
- > Starting from DSH 0.2.1+, the host `webserver` strictly disallows binding directly to wildcard addresses (`0.0.0.0`) for security.
92
- > This plugin now includes a built-in **Smart Adaptive Safe Reverse Proxy Gateway**:
93
- > - **DSH 0.2.1+ (latest, after removing the old `0.0.0.0` config)**: **No `webserver` config is needed**! DSH remains safely bound to `127.0.0.1:3080`, and this plugin launches a standalone secure gateway on `0.0.0.0:3081` by default. Mobile QR codes use `3081` (it auto-increments on port conflicts; always trust the QR shown in settings), while desktop stays on `3080`, avoiding startup failures caused by wildcard-host validation.
94
- > - **DSH 0.1.7 ~ 0.2.0 (Legacy)**: If `host: '0.0.0.0'` is still present in `cordis.patch.yml`, the plugin detects it and switches to `direct` mode, preserving legacy direct access. For custom domains or public IPs, configure `trustedHosts` below.
90
+ Plugin 1.10.0+ does not require changes to `webserver` or `cordis.patch.yml`:
95
91
 
96
- > [!IMPORTANT]
97
- > **⚠️ Upgrade Notice for Existing Users Upgrading to DSH 0.2.1+**:
98
- > If you previously configured `webserver.host: '0.0.0.0'` in `cordis.patch.yml` for remote access, DSH 0.2.1+ will crash on launch due to the official wildcard ban.
99
- > **Please remove that line from your configuration** to restore default loopback listening:
100
- >
101
- > ```yaml
102
- > # ❌ Remove this line when upgrading to 0.2.1+ (otherwise the core rejects to boot):
103
- > # - id: webserver
104
- > # host: '0.0.0.0'
105
- >
106
- > # ✅ Correct setup: Leave webserver unconfigured; DSH naturally binds to 127.0.0.1:3080.
107
- > # This plugin automatically spins up the standalone gateway on 0.0.0.0:3081 for mobile access.
108
- > ```
109
- >
110
- > **Security Considerations**: The gateway binds to `0.0.0.0:3081` by default without built-in TLS. In untrusted or public Wi-Fi networks, using Tailscale (WireGuard encrypted tunnel) or enforcing strong passwords and IP rate-limiting is strongly recommended.
92
+ - DSH 0.2.1+ keeps the default `127.0.0.1:3080`;
93
+ - this plugin starts the `3081` secure access proxy automatically;
94
+ - phones connect via QR pairing or the persistent password.
95
+
96
+ > This section applies to plugin 1.10.0+.
97
+
98
+ > **Security Considerations**: The secure access proxy binds to `0.0.0.0:3081` by default without built-in TLS. In public or untrusted networks, use Tailscale (WireGuard encryption) or an outer HTTPS proxy.
99
+
100
+ <details>
101
+ <summary><b>Migration for configurations created before plugin 1.9.0 (DSH 0.2.1+)</b></summary>
102
+
103
+ Configurations created before plugin 1.9.0, or configurations that manually set `webserver.host: 0.0.0.0`, require the migration below. Plugin 1.10.0+ can skip this section.
104
+
105
+ DSH 0.2.1+ rejects `0.0.0.0` and fails before plugins load. Remove or comment out the entire `webserver` block:
106
+
107
+ ```yaml
108
+ # ❌ This fails to start on DSH 0.2.1+
109
+ # - id: webserver
110
+ # host: '0.0.0.0'
111
+
112
+ # ✅ Keep the default loopback listener:
113
+ # no webserver config is needed; DSH uses 127.0.0.1:3080
114
+ # and dsh-remote-mobile uses the 3081 secure access proxy.
115
+ ```
116
+
117
+ If unsure about the current config:
118
+
119
+ ```bash
120
+ grep -n -A5 'id: webserver' ~/.dsh/profiles/web/cordis.patch.yml
121
+ ```
122
+
123
+ If `host: '0.0.0.0'` is present, remove that block; otherwise no action is needed.
124
+
125
+ DSH 0.1.7 ~ 0.2.0 may keep `0.0.0.0` and the plugin falls back to `direct` mode; for custom domains or public IPs, configure `trustedHosts`.
126
+ </details>
111
127
 
112
128
  If you have installed other plugins that might conflict, we recommend disabling them in `~/.dsh/profiles/web/cordis.patch.yml` (on Windows: `%USERPROFILE%\.dsh\profiles\web\cordis.patch.yml`):
113
129
 
@@ -134,7 +150,72 @@ If you have installed other plugins that might conflict, we recommend disabling
134
150
  dsh web --no-open
135
151
  ```
136
152
 
137
- Open the DSH Web Console in your browser, navigate to **Settings ⚙️ -> Remote & Mobile Access**, and scan the QR code with your mobile camera or WeChat to start using DSH on mobile!
153
+ Open the DSH Web Console in your browser, navigate to **Settings ⚙️ -> Remote & Mobile Access**, and scan the QR code with your mobile camera or WeChat to open the pairing/login page. After authorization, DSH is ready on mobile.
154
+
155
+ ---
156
+
157
+ <span id="modes"></span>
158
+ ## 🧭 Version & Access Modes
159
+
160
+ Plugin 1.10.0+ uses `proxy` by default and does not require `webserver` configuration:
161
+
162
+ | Scenario | DSH core listener | Effective mode | Phone access |
163
+ |---|---|---|---|
164
+ | DSH 0.2.1+ + plugin 1.10.0+ | `127.0.0.1:3080` | `proxy` | `3081` |
165
+ | DSH 0.1.7 ~ 0.2.0 + `0.0.0.0` config | `0.0.0.0:3080` | `direct` (automatic fallback) | `3080` |
166
+ | DSH 0.2.1+ + concrete NIC IP | `192.x.x.x:3080` | `direct` | `3080` |
167
+ | Same-host reverse proxy | `127.0.0.1:3080` + explicit `direct` | `direct` | Proxy entry |
168
+
169
+ - **`proxy`**: recommended; the core stays on loopback and the plugin provides auth/forwarding on `3081`.
170
+ - **`direct`**: no proxy; the core itself must be externally reachable. Intended for DSH 0.1.7 ~ 0.2.0 `0.0.0.0` configurations or advanced deployments.
171
+ - **`direct` + loopback core**: DSH only listens on `127.0.0.1`, so phones cannot reach it directly. It only makes sense when a same-host Nginx / Caddy / Tailscale Serve reverse proxy forwards to `3080`; otherwise it is local-only.
172
+
173
+ #### Access Path & Authentication
174
+
175
+ The decision cannot rely on the TCP source IP (socket address) alone: Host and `X-Forwarded-*` also affect the result. `socket IP` is the TCP peer address seen by the server; it is neither Host nor `X-Forwarded-For`.
176
+
177
+ | Access path | Path | Decision | Plugin login |
178
+ |---|---|---|---|
179
+ | A Local direct | PC → `127.0.0.1:3081` | Local loopback (loopback) | Not required |
180
+ | B Phone direct | Phone → `192.x.x.x:3081` / `100.x.x.x:3081` | External direct (external) | Required (can enable `allowLan`/`allowTailscale` bypass) |
181
+ | C Same-host reverse proxy | Phone → Nginx → `127.0.0.1:3081` | Reverse proxy (reverse-proxy) | Required (no bypass) |
182
+
183
+ “Reverse proxy” here means user-side Nginx / Caddy / Tailscale Serve, not the plugin's `3081` secure access proxy.
184
+
185
+ `127.0.0.1` skips plugin login only for a local direct request with no external Host/Forwarded headers; **there is no switch for this**. Only plugin login is skipped; Host/Origin/CSRF checks still apply. LAN/Tailscale require auth by default, unless `allowLan` / `allowTailscale` is enabled; reverse-proxy traffic does not receive these bypasses.
186
+
187
+ <details>
188
+ <summary><b>Expand: request headers, Nginx config, and common mistakes</b></summary>
189
+
190
+ Header examples (A/B/C as in the table):
191
+
192
+ - A: `Host: 127.0.0.1:3081`
193
+ - B: `Host: 192.x.x.x:3081` or `Host: 100.x.x.x:3081`
194
+ - C: `Host: dsh.example.com` with `X-Forwarded-For: 100.x.x.x` / `X-Forwarded-Host` / `X-Forwarded-Proto: https`
195
+
196
+ Even though Nginx connects from `127.0.0.1`, case C is treated as reverse-proxy traffic and requires authentication.
197
+
198
+ ```nginx
199
+ location / {
200
+ proxy_pass http://127.0.0.1:3081;
201
+ proxy_set_header Host $host;
202
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
203
+ proxy_set_header X-Forwarded-Host $host;
204
+ proxy_set_header X-Forwarded-Proto $scheme;
205
+ }
206
+ ```
207
+
208
+ Add custom domains to `trustedHosts`:
209
+
210
+ ```json
211
+ {
212
+ "trustedHosts": ["dsh.example.com"]
213
+ }
214
+ ```
215
+
216
+ Common mistake: rewriting Host to `127.0.0.1` and stripping all `X-Forwarded-*` makes the plugin treat the request as case A and skip login. Tailscale Serve is also a same-host proxy and must preserve these headers.
217
+
218
+ </details>
138
219
 
139
220
  ---
140
221
 
@@ -163,7 +244,7 @@ Open the DSH Web Console in your browser, navigate to **Settings ⚙️ -> Remot
163
244
  ## 🌟 Features
164
245
 
165
246
  ### 1. Network Access
166
- - **Tailscale Private Network**: Automatically detects local Tailscale IP (`100.64.0.0/10` CGNAT subnet), creates pairing QR codes, and supports direct passwordless bypass (transport encryption guaranteed by Tailscale WireGuard tunnels).
247
+ - **Tailscale Private Network**: Automatically detects local Tailscale IP (`100.64.0.0/10` CGNAT subnet), creates pairing QR codes, and supports direct passwordless bypass (transport encryption provided by Tailscale's WireGuard tunnel).
167
248
  - **Local Area Network (LAN / Wi-Fi)**: Automatically detects RFC 1918 private IPs (`192.168.x.x`, `10.x.x.x`, `172.16-31.x.x`), generates LAN pairing QR codes with direct link previews and prominent high-risk security warnings.
168
249
  - **Instant QR Pairing**: Scan QR codes directly with any mobile camera or scanner app to authenticate.
169
250
 
@@ -174,25 +255,27 @@ Open the DSH Web Console in your browser, navigate to **Settings ⚙️ -> Remot
174
255
  - **Device Session Management**: Real-time inspection of connected device types, OS, browser, source IP, and last active timestamp, with single-device revocation and one-click bulk revocation.
175
256
 
176
257
  ### 3. Security & Cryptography
177
- - **Transport RSA Asymmetric Encryption**: The login endpoint encrypts on the client first, so sensitive passwords and pairing codes travel the network only in ciphertext.
258
+ - **Application-Layer Login Credential Encryption**: The Web login page encrypts credentials on the client before submitting them, so sensitive passwords and pairing codes are sent as ciphertext. If encryption fails, submission stops with an error instead of falling back to plaintext.
178
259
  - **Secure contexts (HTTPS)**: prioritizes native Web Crypto `RSA-OAEP-SHA256`;
179
- - **DSH default HTTP LAN/Tailscale access (non-secure context)**: falls back to a pure JS shim using `crypto.getRandomValues()` for cryptographically secure random padding.
260
+ - **DSH default HTTP LAN/Tailscale access (browser non-secure context; Tailscale's WireGuard tunnel is still encrypted)**: falls back to a pure JS shim using `crypto.getRandomValues()` for cryptographically secure random padding.
261
+
262
+ > The plugin does not provide TLS itself; page/API/WebSocket traffic needs Tailscale or an outer HTTPS proxy for full transport encryption.
180
263
  - **scrypt Slow Hash Storage**: Server persists passwords with `scrypt` salted slow-hashing (`scrypt:${salt}:${hash}`). Verification uses `crypto.timingSafeEqual` constant-time comparison to prevent timing side-channel attacks.
181
264
  - **Brute-Force Defense & Rate Limiting**:
182
265
  - Automatically locks out IPs for 15 minutes after reaching consecutive failed attempt thresholds (default: 5), returning HTTP 429;
183
266
  - Sliding-window rate limiting (default: 60 visits/min) to prevent high-frequency brute-force scraping;
184
267
  - Audit logs and lockout states persist to disk and restore across restarts;
185
268
  - Administrators can manually unlock blocked IPs with one click in the panel.
186
- - **Intelligent Static Asset Whitelisting**: The authentication gate automatically allows verified frontend asset extensions (`.js`, `.css`, `.png`, `.svg`, `.woff2`, etc., 20 types) while blocking extensionless or dynamic API routes (e.g. `/plugins/xxx/admin`), ensuring smooth third-party plugin rendering without manual whitelist configuration. (The DSH core static directory `/assets/` is an official resource and is allowed unconditionally.)
269
+ - **Intelligent Static Asset Whitelisting**: The authentication gate allows known frontend asset extensions (`.js`, `.css`, `.png`, `.svg`, `.woff2`, etc., 20 types) while blocking extensionless or dynamic API routes (e.g. `/plugins/xxx/admin`), reducing the exposed dynamic-API surface while remaining compatible with third-party frontend assets, without manual whitelist configuration. (The DSH core static directory `/assets/` is an official resource and is allowed unconditionally.)
187
270
  - **Resilient Atomic Persistence & Debounce**: Millisecond-level debounce write-throttling to safeguard disk I/O (500ms for session data, 300ms for style snippets), coupled with `beforeExit` process flush hooks and atomic temporary file replacement (`renameSync`) for `settings.json` and `style-snippets.json`.
188
271
  - **Source IP Extraction**: By default only the underlying Socket address is trusted; when a request is recognized as a trusted reverse proxy (via Host / `X-Forwarded-*`), the first valid `X-Forwarded-For` IP is used instead, balancing real-source attribution with anti-spoofing.
189
- - **Cross-Site Write Defense**: All non-GET/HEAD requests validate browser same-origin signals (`Origin` / `Sec-Fetch-Site`). Cross-site writes driven by malicious webpages against `127.0.0.1` are rejected outright; non-browser clients (curl / local scripts) are unaffected. Note: this policy also blocks cross-origin webhooks / OAuth callbacks from third-party plugins and legacy WebViews that send `Origin: null`; validate compatibility before using such integrations.
190
- - **Context & Proxy Boundary**: Same-origin/local-variant Host and Origin are normalized as a pair, while malicious cross-site Origins are preserved for downstream CSRF checks. A same-host `127.0.0.1` proxy is recognized via an external Host, and a LAN/Tailscale proxy via `X-Forwarded-*`, so neither is mistaken for a passwordless local client; they must authenticate with the plugin token. The plugin's own trusted gateway is exempted via an internal secret. Loopback requests carrying the plugin's own 127-valued Forwarded headers are recognized as internal and do not break the local 3080 root connection auth. The SSE event stream accepts same-origin connections only, with no cross-origin reads.
272
+ - **Cross-Site Write Defense**: All write requests carrying browser same-origin signals validate (`Origin` / `Sec-Fetch-Site`). Cross-site writes driven by malicious webpages against `127.0.0.1` are rejected outright; non-browser clients (curl / local scripts) are not subject to this check. Note: this policy also blocks cross-origin webhooks / OAuth callbacks from third-party plugins and legacy WebViews that send `Origin: null`; validate compatibility before using such integrations.
273
+ - **Context & Proxy Boundary**: Same-origin/local-variant Host and Origin are normalized as a pair, while malicious cross-site Origins are preserved for downstream CSRF checks. A same-host `127.0.0.1` proxy is recognized via an external Host, and a LAN/Tailscale proxy via `X-Forwarded-*`, so neither is mistaken for a passwordless local client; they must authenticate with the plugin token. The plugin's own trusted proxy is exempted via an internal secret. Loopback requests carrying the plugin's own 127-valued Forwarded headers are recognized as internal and do not break the local 3080 root connection auth. The SSE event stream accepts same-origin connections only, with no cross-origin reads.
191
274
 
192
275
  ### 4. Real-Time Sync & Internationalization
193
276
  - **SSE Real-Time Push**: Push notifications for new device pairing, reconnections, revocations, and security alerts via Server-Sent Events, with bidirectional close listeners and idempotent cleanup.
194
277
  - **Adaptive Bilingual UI**: Automatically switches between English and Simplified Chinese based on DSH settings and browser language preferences.
195
- - **Non-HTTPS Compatibility Patch**: Injects `crypto.randomUUID` and `navigator.clipboard` Polyfills into HTML templates to fix missing native Web Crypto / Clipboard APIs in HTTP non-secure mobile browser contexts (the clipboard polyfill keeps copy and snippet-copy working in non-secure contexts).
278
+ - **Non-HTTPS Compatibility Patch**: Injects `crypto.randomUUID` and `navigator.clipboard` Polyfills into HTML templates to address missing native Web Crypto / Clipboard APIs in HTTP non-secure mobile browser contexts (the clipboard polyfill provides copy and snippet-copy support in non-secure contexts).
196
279
 
197
280
  ### 5. Mobile Style Snippets (style mini-plugins)
198
281
 
@@ -209,7 +292,7 @@ The DSH web UI still carries many desktop-oriented styles on phones. This plugin
209
292
 
210
293
  ### 6. Generic Plugin Coexistence Protection
211
294
 
212
- - **Conflict-free startup**: when the shared pairing service `remoteWebUiPairing` is already registered by another remote/Web access plugin, this plugin yields automatically (deferred arbitration: it waits until the activation window settles, then checks who owns the service name), avoiding the whole-tree rollback caused by service-name collisions — installing from the plugin market works out of the box with zero manual configuration;
295
+ - **Automatic coexistence yield**: when the shared pairing service `remoteWebUiPairing` is already registered by another remote/Web access plugin, this plugin yields automatically (deferred arbitration: it waits until the activation window settles, then checks who owns the service name), avoiding the whole-tree rollback caused by service-name collisions — installing from the plugin market usually works without manually disabling the other plugin;
213
296
  - **Visible coexistence status**: when a conflict is detected, a dismissible warning banner (Chinese/English) appears at the top of the settings panel, explaining the yield behavior and the "keep only one" recommendation; refresh after resolving and it disappears;
214
297
  - **One-click diagnosis report**: the **"📋 Copy diagnosis & fix"** button in the banner copies a full report — the conflicting plugin's package name and loader entry id are auto-detected at runtime, and the report includes line-exact fix configuration and commands (disable the other / remove this one), ready to paste to an AI assistant.
215
298
 
@@ -301,7 +384,17 @@ dsh plugin --profile web remove dsh-remote-mobile
301
384
  <span id="advanced"></span>
302
385
  ## ⚙️ Advanced Configuration
303
386
 
304
- Configurations persist in the plugin-owned file `~/.dsh/remote-mobile/settings.json`; adjust them in the Web UI or edit the file directly:
387
+ Configurations persist in the plugin-owned file `~/.dsh/remote-mobile/settings.json`. In the Web settings page:
388
+
389
+ | Configuration | Location | Apply |
390
+ |---|---|---|
391
+ | `mode` / `proxyPort` / `proxyHost` / `trustedHosts` | Access → Access Proxy Configuration | Restart DSH |
392
+ | Tailscale / LAN bypass switches | Access → Network Access & Bypass Management | Immediate |
393
+ | Persistent access password | Access → Configure Persistent Password (Optional) | Immediate |
394
+ | Rate limit / failure threshold / lockout | Devices & Security → Global Security Policy Configuration | Immediate |
395
+ | Style snippets | Style Overrides | Next page load |
396
+
397
+ You can also edit `settings.json` directly. Restart DSH after changing the access mode or listener settings:
305
398
 
306
399
  ```jsonc
307
400
  {
@@ -311,18 +404,20 @@ Configurations persist in the plugin-owned file `~/.dsh/remote-mobile/settings.j
311
404
  "maxVisitsPerMinute": 60, // number, default 60: max login page visits per minute per IP
312
405
  "maxFailedAttempts": 5, // number, default 5: max consecutive failed attempts before IP lockout
313
406
  "lockDurationMs": 900000, // number, default 900000 (15 mins): IP lockout duration in ms
314
- "gatewayPort": 3081, // number, default 3081: standalone secure gateway port
315
- "gatewayHost": "0.0.0.0", // string, default 0.0.0.0: gateway listen address
316
- "mode": "auto", // auto | gateway | direct: auto is recommended
407
+ "proxyPort": 3081, // number, default 3081: secure proxy port
408
+ "proxyHost": "0.0.0.0", // string, default 0.0.0.0: secure proxy listen address
409
+ "mode": "proxy", // proxy | direct: proxy is the default (recommended)
317
410
  "trustedHosts": [ // string[], default []: trusted domain whitelist (e.g. Tailscale MagicDNS or reverse proxy domain)
318
411
  "my-device.tailnet.ts.net"
319
412
  ]
320
413
  }
321
414
  ```
322
415
 
323
- > **About `trustedHosts`**: If accessing DSH via Tailscale MagicDNS (e.g. `xxx.tailnet.ts.net`) or an external reverse proxy domain, add bare hostnames to this array. Both the gateway and core will accept them in the host whitelist and plugin trust fences, preventing 400 or 403 errors. The same whitelist applies to **direct mode**: when accessing via a custom domain, public IP, or a non-local hostname, the actual Host must be listed here; local NIC IPs and loopback are recognized automatically.
416
+ > **Terminology**: In this document, “Secure Access Proxy” refers to the reverse-proxy entry point provided by this plugin on port `3081`; it handles authentication, Host/Origin validation, and request forwarding. It is **not** a network default gateway.
417
+ >
418
+ > **About `trustedHosts`**: If accessing DSH via Tailscale MagicDNS (e.g. `xxx.tailnet.ts.net`) or an external reverse proxy domain, add bare hostnames to this array. Both the secure proxy and core will accept them in the host whitelist and plugin trust fences, preventing 400 or 403 errors. The same whitelist applies to **direct mode**: when accessing via a custom domain, public IP, or a non-local hostname, the actual Host must be listed here; local NIC IPs and loopback are recognized automatically.
324
419
  >
325
- > **About `gatewayPort` / `gatewayHost` / `mode`**: defaults are usually sufficient. `mode: 'gateway'` requires the core to be reachable via loopback; forcing a **concrete LAN IP** falls back to `direct` with a warning; using `0.0.0.0` / `::` starts the gateway but explicitly warns that core port 3080 remains exposed. Restart DSH after changing these advanced options.
420
+ > **About `proxyPort` / `proxyHost` / `mode`**: defaults are usually sufficient. `mode: 'proxy'` is the default and requires the core to listen on loopback; if the core is externally bound (`0.0.0.0` or a concrete NIC IP), the plugin automatically falls back to `direct` with a warning. `mode: 'direct'` starts no secure proxy and requires the core to be externally reachable; with a loopback core, `direct` only works behind a same-host reverse proxy and phones cannot connect directly. Restart DSH after changing these advanced options.
326
421
  >
327
422
  > **HTTPS reverse proxy note**: When using Nginx/Caddy/Tailscale Serve with HTTPS, make sure to:
328
423
  > 1. **Preserve the external Host** (Nginx: `proxy_set_header Host $host`; do not rewrite it to `127.0.0.1`);
@@ -368,11 +463,13 @@ Custom snippets (style mini-plugins) and their enabled states are persisted in `
368
463
 
369
464
  | Path | Description | Security Level |
370
465
  |---|---|---|
371
- | `~/.dsh/remote-mobile/settings.json` | Plugin config: bypass toggles, persistent-password hash, rate-limit & lockout policy | Local storage, `0o600` (Restricted to current user) |
466
+ | `~/.dsh/remote-mobile/settings.json` | Plugin config: bypass toggles, persistent-password hash, rate-limit & lockout policy, access mode and proxy listener settings | Local storage, `0o600` (Restricted to current user) |
372
467
  | `~/.dsh/remote-mobile/devices.json` | Authorized devices, sessions & IP audit statistics (contains long-lived tokens, `0o600`) | Local storage, restricted to current user |
373
468
  | `~/.dsh/remote-mobile/rsa-keys.json` | Server RSA keypair (public & private) | Local storage, `0o600` (Restricted to current user) |
374
469
  | `~/.dsh/remote-mobile/style-snippets.json` | Mobile style snippets (built-in toggles + custom CSS mini-plugins) | Local persistent storage |
375
470
 
471
+ > `0o600` is a POSIX permission; Windows uses user-directory ACLs instead of POSIX mode bits.
472
+
376
473
  ---
377
474
 
378
475
  <span id="faq"></span>
@@ -381,7 +478,7 @@ Custom snippets (style mini-plugins) and their enabled states are persisted in `
381
478
  <details>
382
479
  <summary><b>Q0: Startup fails with <code>listen EADDRINUSE: address already in use 0.0.0.0:3080 / :3081</code>?</b></summary>
383
480
 
384
- **Answer**: This means **port 3080 (DSH core) or 3081 (plugin gateway) is held by another process** (in most cases a previous `dsh web` instance has not fully exited — e.g. a hidden instance launched by a desktop shortcut, or overlapping old/new processes during restart). It is **unrelated to which plugins are installed**. Troubleshooting:
481
+ **Answer**: This means **port 3080 (DSH core) or 3081 (plugin secure proxy) is held by another process** (in most cases a previous `dsh web` instance has not fully exited — e.g. a hidden instance launched by a desktop shortcut, or overlapping old/new processes during restart). It is **unrelated to which plugins are installed**. Troubleshooting:
385
482
 
386
483
  1. **Find the process holding the port**:
387
484
  * **macOS / Linux**: `lsof -nP -iTCP:3080` / `lsof -nP -iTCP:3081`
@@ -399,24 +496,30 @@ If your log instead shows `service "remoteWebUiPairing" has been registered`, pl
399
496
  <summary><b>Q1: Getting "Connection Refused" or cannot open page when scanning on mobile?</b></summary>
400
497
 
401
498
  **Answer**: Please verify the following items step by step:
402
- 1. **Configuration**: On DSH 0.2.1+, do **not** set `host: '0.0.0.0'` in `cordis.patch.yml` (it will fail to start). Keep DSH's default `127.0.0.1:3080`; the plugin starts the `3081` gateway automatically. Only legacy DSH (0.1.7 ~ 0.2.0, where `0.0.0.0` is still allowed) or an explicit `direct` mode (concrete NIC IP) needs a listen address. Restart DSH after any config change.
499
+ 1. **Configuration**: On DSH 0.2.1+, do **not** set `host: '0.0.0.0'` in `cordis.patch.yml` (it will fail to start). Keep DSH's default `127.0.0.1:3080`; the plugin starts the `3081` secure proxy automatically. DSH 0.1.7 ~ 0.2.0 may use `0.0.0.0`; an explicit `direct` mode requires a concrete NIC IP. Restart DSH after any config change.
403
500
  2. **Same Network**: For LAN access, ensure your mobile phone and computer are connected to the exact same Wi-Fi router, and disable standalone VPN/proxy apps on your phone.
404
501
  3. **Windows Defender Firewall (Common for Windows users)**:
405
502
  * When prompted by the Windows Firewall popup on startup, ensure both **"Private networks"** and **"Public networks"** are checked before allowing access;
406
- * If you missed the prompt or still cannot connect, run the following command in **PowerShell as Administrator** to allow the gateway port (default `3081`; use `3080` if you are in `direct` mode):
503
+ * If you missed the prompt or still cannot connect, run the following command in **PowerShell as Administrator** to allow the proxy port (default `3081`; use `3080` if you are in `direct` mode):
407
504
  ```powershell
408
- New-NetFirewallRule -DisplayName "DSH Remote Gateway 3081" -Direction Inbound -LocalPort 3081 -Protocol TCP -Action Allow
505
+ New-NetFirewallRule -DisplayName "DSH Remote Proxy 3081" -Direction Inbound -LocalPort 3081 -Protocol TCP -Action Allow
409
506
  ```
410
507
  * **Check Wi-Fi Network Profile**: Under Windows "Settings -> Network & Internet -> Wi-Fi", make sure your current Wi-Fi profile is set to **"Private network"** (Windows "Public network" blocks inbound LAN connections by default);
411
- 4. **Virtual / WSL Network Adapters**: If WSL2 or Hyper-V is installed on Windows, virtual network adapters (e.g. `172.x.x.x`) may be present. Make sure you use the physical WLAN / Wi-Fi LAN IP or the Tailscale QR code shown in the plugin settings panel. If the gateway port auto-incremented, use the actual port shown in the settings panel / terminal banner.
508
+ 4. **Virtual / WSL Network Adapters**: If WSL2 or Hyper-V is installed on Windows, virtual network adapters (e.g. `172.x.x.x`) may be present. Make sure you use the physical WLAN / Wi-Fi LAN IP or the Tailscale QR code shown in the plugin settings panel. If the proxy port auto-incremented, use the actual port shown in the settings panel / terminal banner.
412
509
  </details>
413
510
 
414
511
  <details>
415
512
  <summary><b>Q2: What is the difference between Tailscale Bypass and LAN Bypass?</b></summary>
416
513
 
417
514
  **Answer**:
418
- * **Tailscale Bypass**: Highly secure. Tailscale operates over an encrypted WireGuard overlay where only devices logged into your account can connect.
419
- * **LAN Bypass**: Inherent risks. Anyone connected to your Wi-Fi (including guests or unauthorized devices) can control your workspace. **Never enable this on untrusted networks**.
515
+ * **Tailscale Bypass**: Reasonably secure. Tailscale operates over an encrypted WireGuard overlay; by default, only devices in the same tailnet or explicitly authorized by ACL/sharing can connect.
516
+ * **LAN Bypass**: Inherent risks. When enabled, any LAN device that can reach the gateway port (including guests or unauthorized devices) may control your workspace; guest-network isolation or a firewall may block it, but do not treat that as a security boundary. **Never enable this on untrusted networks**.
517
+ </details>
518
+
519
+ <details>
520
+ <summary><b>Q3: Does mobile support creating and switching workspaces normally?</b></summary>
521
+
522
+ **Answer**: **Supported.** The plugin uses request isolation so that mobile devices get the same workspace management and execution capabilities as the desktop Web interface. If tapping "Select folder" opens the dialog on the PC instead of the phone, see [Q12](#q12).
420
523
  </details>
421
524
 
422
525
  <details>
@@ -435,7 +538,7 @@ If your log instead shows `service "remoteWebUiPairing" has been registered`, pl
435
538
  <details>
436
539
  <summary><b>Q5: What does the yellow banner "⚠️ Another remote-access plugin detected" at the top of the settings panel mean?</b></summary>
437
540
 
438
- **Answer**: It means another remote/Web access plugin is enabled alongside this one and registered the shared pairing service first. To keep startup safe, this plugin has **automatically yielded** that service (its own security gateway and remote access are unaffected), but running two remote-access features side by side may cause duplicated entries. Keeping only one is recommended:
541
+ **Answer**: It means another remote/Web access plugin is enabled alongside this one and registered the shared pairing service first. To keep startup safe, this plugin has **automatically yielded** that service (its own secure proxy and remote access are unaffected), but running two remote-access features side by side may cause duplicated entries. Keeping only one is recommended:
439
542
 
440
543
  1. Click **"📋 Copy diagnosis & fix"** in the banner and paste the report to an AI assistant (the report already identifies the conflicting plugin's package name and entry id, with exact fix configuration);
441
544
  2. To keep this plugin: follow the report to append the `disabled: true` lines for the other plugin at the end of `~/.dsh/profiles/web/cordis.patch.yml`, then restart DSH;
@@ -470,7 +573,7 @@ If your log instead shows `service "remoteWebUiPairing" has been registered`, pl
470
573
  * By default the plugin trusts the physical socket only. A **same-host `127.0.0.1` proxy carrying an external Host**, or **any proxy carrying `X-Forwarded-*`**, is recognized as **proxied traffic** and no longer receives `loopback` / `allowLan` / `allowTailscale` passwordless access; it must authenticate with the plugin token (pairing code or persistent password).
471
574
  * Make sure the reverse proxy **preserves the external Host** (do not rewrite it to `127.0.0.1`) and forwards `X-Forwarded-For`, `X-Forwarded-Host`, and `X-Forwarded-Proto: https`. Caddy / Tailscale Serve usually handle this automatically; Nginx requires explicit configuration. If Host is rewritten to `127.0.0.1` with no Forwarded headers, the plugin cannot distinguish a local browser from a same-host proxy.
472
575
  * If you want the plugin pairing code/password enforced even through an external unified domain, configure the proxy as above so the plugin can identify the real source and apply token auth. Do not rely on the NAS/host gateway login as a replacement for plugin authentication.
473
- * If you do not need gateway mode, you may use `direct` mode with a concrete NIC IP; on DSH 0.2.1+ a wildcard `0.0.0.0` is not allowed.
576
+ * If you do not need proxy mode, you may use `direct` mode with a concrete NIC IP; on DSH 0.2.1+ a wildcard `0.0.0.0` is not allowed.
474
577
  </details>
475
578
 
476
579
  <details>
@@ -486,11 +589,13 @@ If your log instead shows `service "remoteWebUiPairing" has been registered`, pl
486
589
  <details>
487
590
  <summary><b>Q9: DSH 0.2.1+ fails to start with <code>host "0.0.0.0" is an unspecified (wildcard) address</code>?</b></summary>
488
591
 
489
- **Answer**: This is DSH 0.2.1+'s official security restriction, not a plugin issue. The error happens before the plugin loads, so the gateway cannot take over yet; you must fix it manually:
592
+ **Answer**: This is DSH 0.2.1+'s official security restriction, not a plugin issue. The error happens before the plugin loads, so the secure proxy cannot take over yet; you must fix it manually:
490
593
 
491
594
  1. Open `~/.dsh/profiles/web/cordis.patch.yml` (Windows: `%USERPROFILE%\.dsh\profiles\web\cordis.patch.yml`);
492
595
  2. Remove or comment out the `webserver.host: '0.0.0.0'` line added previously for remote access (if the whole `- id: webserver ... host: '0.0.0.0'` block exists only for this, remove the block);
493
- 3. Restart `dsh web --no-open`. DSH returns to `127.0.0.1:3080`, this plugin starts the `0.0.0.0:3081` gateway automatically, and mobile devices can scan the QR code from the settings panel.
596
+ 3. Restart `dsh web --no-open`. DSH returns to `127.0.0.1:3080`, this plugin starts the `0.0.0.0:3081` secure proxy automatically, and mobile devices can scan the QR code from the settings panel.
597
+
598
+ > 1.10.0+ appends a `[dsh-remote-mobile] startup hint` after the wildcard-address exit, including the exact config file path and remediation. Other startup errors do not trigger it.
494
599
  </details>
495
600
 
496
601
  <details>
@@ -501,12 +606,12 @@ If your log instead shows `service "remoteWebUiPairing" has been registered`, pl
501
606
  - `http://127.0.0.1:3080/api/remote-mobile/status` returns 200;
502
607
  - `http://127.0.0.1:3081/` returns 200;
503
608
 
504
- then the plugin is loaded and the gateway is active, but the current process's connection auth is still not allowing the local root request. The common cause is that a **hot reload/hot update did not reload the plugin code**, or an older build is running.
609
+ then the plugin is loaded and the secure proxy is active, but the current process's connection auth is still not allowing the local root request. The common cause is that a **hot reload/hot update did not reload the plugin code**, or an older build is running.
505
610
 
506
611
  Steps:
507
612
 
508
613
  1. Confirm the installed plugin is **1.10.0+**, then **fully stop and restart** `dsh web` (refreshing the browser is not enough);
509
- 2. As a temporary workaround, use `http://127.0.0.1:3081/` (the plugin gateway), or open the `?token=...` URL printed by `dsh web` once to set the official cookie;
614
+ 2. As a temporary workaround, use `http://127.0.0.1:3081/` (the plugin secure proxy), or open the `?token=...` URL printed by `dsh web` once to set the official cookie;
510
615
  3. If using a reverse proxy, preserve the external Host and forward `X-Forwarded-*` as described in Q7;
511
616
  4. If it persists after restart, report the DSH version, plugin version, and startup logs.
512
617
 
@@ -526,6 +631,49 @@ Steps:
526
631
  1.10.0 adds test isolation: tests now use a temporary `/tmp/dsh-rm-test-devices-*.json` by default, so they no longer pollute the real device file. Existing fake sessions can be removed with "Revoke all devices" followed by re-pairing the current devices, or by stopping DSH and removing the matching entries from `devices.json` after taking a backup.
527
632
  </details>
528
633
 
634
+ <details id="q12">
635
+ <summary><b>Q12: Why does tapping "Select folder" on a phone over Tailscale / 3081 open a dialog on the PC instead of the phone?</b></summary>
636
+
637
+ **Answer**: This is not Tailscale or the phone browser. It is caused by DSH's official directory-picker "local vs remote" detection:
638
+
639
+ 1. **The workspace directory lives on the PC**: DSH's "Select folder" chooses a directory on the machine running DSH, not on the phone. A phone browser cannot open the PC's native file chooser.
640
+ 2. **Auto decides only from the boot environment**: `dsh-host-directory-picker-auto` checks `webServer.host` at startup. With a `127.0.0.1` core, no SSH launch, and a desktop session, it selects `native`, which opens the PC's macOS / Windows folder chooser.
641
+ 3. **The secure proxy makes remote requests look local**: this plugin keeps the DSH core on `127.0.0.1:3080` and serves remote clients through the `0.0.0.0:3081` secure proxy. When a phone reaches 3081 over Tailscale, the core still sees a loopback request, so auto assumes the operator is sitting at the PC and opens the native chooser there. The phone cannot render that host-side window and simply waits.
642
+
643
+ **Fix (One-click in Web UI recommended, or manual configuration)**:
644
+
645
+ - **Option 1 (One-click enable / restore in Web UI with automatic deduplication)**: Open DSH Web UI -> **Settings -> Remote & Mobile Access**, and click **"⚡ Enable In-App Directory Browser"** in the top reminder banner (or inside the "Mobile Workspace Directory Picker Mode" section of the Access tab). The plugin will safely write the patch into `~/.dsh/profiles/web/cordis.patch.yml` with strict deduplication. You can also click **"🔄 Restore PC Native Picker"** at any time to revert.
646
+ - **Option 2 (Manual file edit)**: Pin the in-app `browse` picker. Append to `~/.dsh/profiles/web/cordis.patch.yml` (Windows: `%USERPROFILE%\.dsh\profiles\web\cordis.patch.yml`):
647
+
648
+ ```yaml
649
+ - id: directory-picker
650
+ disabled: true
651
+ - insert:
652
+ - id: directory-picker-browse
653
+ name: '@deepseek-ai/dsh-host-directory-picker-browse'
654
+ - id: ui-directory-picker-browse
655
+ name: '@deepseek-ai/dsh-client-ui-directory-picker-browse'
656
+ ```
657
+
658
+ > **Note**: In DSH's patch layer, `name` asserts an existing plugin name; it does not rename. Do not simply change `directory-picker`'s `name` to browse, or the patch will be skipped. Disable auto first, then use an id-less root `insert` to add the browse backend and browser UI.
659
+
660
+ Save the file. DSH with HMR usually reloads automatically; otherwise fully restart `dsh web`. Hard-refresh the phone page, then "Select folder" opens the in-app directory browser (breadcrumbs, directory list, create subdirectory); both phone and PC use the web picker.
661
+
662
+ **Trade-off**: The PC also loses the native chooser. If you want native on the PC and browse on the phone, use a separate remote profile or a custom hybrid picker; DSH's auto picker does not switch per client. The browse picker still requires the existing DSH / plugin authentication and does not bypass it.
663
+ </details>
664
+
665
+ ---
666
+
667
+ ## Star History
668
+
669
+ <a href="https://www.star-history.com/?repos=iceapriler%2Fdsh-remote-mobile&type=date&legend=bottom-right">
670
+ <picture>
671
+ <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=iceapriler/dsh-remote-mobile&type=date&theme=dark&legend=bottom-right" />
672
+ <source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=iceapriler/dsh-remote-mobile&type=date&legend=bottom-right" />
673
+ <img alt="Star History Chart" src="https://api.star-history.com/chart?repos=iceapriler/dsh-remote-mobile&type=date&legend=bottom-right" />
674
+ </picture>
675
+ </a>
676
+
529
677
  ---
530
678
 
531
679
  ## 📄 License
package/lib/auth/gate.js CHANGED
@@ -268,7 +268,7 @@ export function createGlobalAuthGate(store) {
268
268
  }
269
269
  // 1.5 非回环请求必须先通过 Host 白名单(DNS-Rebinding / 恶意 Host 防御)。
270
270
  // GatewayServer 在转发前已经用自己的 Host 白名单校验过,并打上 __dsh_rm_host_allowed__,
271
- // 这里避免对同一请求重复校验(尤其是 gatewayHost 为自定义域名时)。
271
+ // 这里避免对同一请求重复校验(尤其是 proxyHost 为自定义域名时)。
272
272
  if (req.headers.host &&
273
273
  !req.__dsh_rm_host_allowed__ &&
274
274
  !isTrustedHost(req.headers.host, store)) {
@@ -106,9 +106,9 @@ export interface SessionStoreOptions {
106
106
  maxFailedAttempts?: number;
107
107
  lockDurationMs?: number;
108
108
  maxVisitsPerMinute?: number;
109
- gatewayPort?: number;
110
- gatewayHost?: string;
111
- mode?: 'auto' | 'gateway' | 'direct';
109
+ proxyPort?: number;
110
+ proxyHost?: string;
111
+ mode?: 'proxy' | 'direct';
112
112
  trustedHosts?: string[];
113
113
  }
114
114
  /**
@@ -124,22 +124,22 @@ export declare class SessionStore extends EventEmitter {
124
124
  private debounceTimer;
125
125
  readonly persistPath: string;
126
126
  readonly settingsFilePath: string | null;
127
- /** 网关运行时状态契约 */
127
+ /** 安全接入代理运行时状态契约 */
128
128
  private gatewayStatus;
129
129
  constructor(options?: SessionStoreOptions);
130
130
  getOptions(): Required<SessionStoreOptions>;
131
- /** 设置网关当前生效的运行时参数与状态契约 */
132
- setActiveGatewayInfo(mode: 'gateway' | 'direct', activePort: number, corePort: number, running?: boolean, error?: string | null): void;
133
- /** 获取网关完整运行状态对象 (供 /api/remote-mobile/status 消费) */
131
+ /** 设置安全接入代理当前生效的运行时参数与状态契约 */
132
+ setActiveGatewayInfo(mode: 'proxy' | 'direct', activePort: number, corePort: number, running?: boolean, error?: string | null): void;
133
+ /** 获取安全接入代理完整运行状态对象 (供 /api/remote-mobile/status 消费) */
134
134
  getGatewayStatus(): {
135
- mode: 'gateway' | 'direct';
135
+ mode: 'proxy' | 'direct';
136
136
  running: boolean;
137
137
  port: number;
138
138
  corePort: number;
139
139
  error: string | null;
140
140
  };
141
- /** 获取当前实际对外提供访问的模式 (gateway | direct) */
142
- getActiveMode(): 'gateway' | 'direct';
141
+ /** 获取当前实际对外提供访问的模式 (proxy | direct) */
142
+ getActiveMode(): 'proxy' | 'direct';
143
143
  /** 获取当前实际对外提供访问的端口 (3081 或 3080) */
144
144
  getActivePort(): number;
145
145
  /** 获取底层 DSH 本地核心端口 (通常为 3080) */
package/lib/auth/token.js CHANGED
@@ -188,12 +188,15 @@ export function readPluginSettingsJson(filePath = SETTINGS_FILE) {
188
188
  result.lockDurationMs = data.lockDurationMs;
189
189
  if (typeof data.maxVisitsPerMinute === 'number' && data.maxVisitsPerMinute > 0)
190
190
  result.maxVisitsPerMinute = data.maxVisitsPerMinute;
191
- if (typeof data.gatewayPort === 'number' && data.gatewayPort > 0)
192
- result.gatewayPort = data.gatewayPort;
193
- if (typeof data.gatewayHost === 'string' && data.gatewayHost)
194
- result.gatewayHost = data.gatewayHost;
195
- if (data.mode === 'auto' || data.mode === 'gateway' || data.mode === 'direct')
191
+ if (typeof data.proxyPort === 'number' && data.proxyPort > 0)
192
+ result.proxyPort = data.proxyPort;
193
+ if (typeof data.proxyHost === 'string' && data.proxyHost)
194
+ result.proxyHost = data.proxyHost;
195
+ if (data.mode === 'proxy' || data.mode === 'direct')
196
196
  result.mode = data.mode;
197
+ // 1.10 之前的过渡值 auto 归一化为默认 proxy;核心非回环时会自动按 direct 运行。
198
+ else if (data.mode === 'auto')
199
+ result.mode = 'proxy';
197
200
  if (Array.isArray(data.trustedHosts)) {
198
201
  result.trustedHosts = data.trustedHosts.filter((h) => typeof h === 'string' && h.trim());
199
202
  }
@@ -220,9 +223,9 @@ export function writePluginSettingsJson(options, filePath = SETTINGS_FILE) {
220
223
  maxFailedAttempts: options.maxFailedAttempts || 5,
221
224
  lockDurationMs: options.lockDurationMs || 900000,
222
225
  maxVisitsPerMinute: options.maxVisitsPerMinute || 60,
223
- gatewayPort: options.gatewayPort || 3081,
224
- gatewayHost: options.gatewayHost || '0.0.0.0',
225
- mode: options.mode || 'auto',
226
+ proxyPort: options.proxyPort || 3081,
227
+ proxyHost: options.proxyHost || '0.0.0.0',
228
+ mode: options.mode === 'direct' ? 'direct' : 'proxy',
226
229
  trustedHosts: Array.isArray(options.trustedHosts) ? options.trustedHosts : [],
227
230
  };
228
231
  writeFileSync(tmpPath, JSON.stringify(payload, null, 2), { encoding: 'utf8', mode: 0o600 });
@@ -290,9 +293,9 @@ export function resolveInitialStoreOptions(explicit = {}, own = null, legacy = {
290
293
  maxFailedAttempts: merged.maxFailedAttempts ?? 5,
291
294
  lockDurationMs: merged.lockDurationMs ?? 15 * 60 * 1000,
292
295
  maxVisitsPerMinute: merged.maxVisitsPerMinute ?? 60,
293
- gatewayPort: merged.gatewayPort ?? 3081,
294
- gatewayHost: merged.gatewayHost ?? '0.0.0.0',
295
- mode: merged.mode ?? 'auto',
296
+ proxyPort: merged.proxyPort ?? 3081,
297
+ proxyHost: merged.proxyHost ?? '0.0.0.0',
298
+ mode: merged.mode === 'direct' ? 'direct' : 'proxy',
296
299
  trustedHosts: Array.isArray(merged.trustedHosts) ? merged.trustedHosts : [],
297
300
  };
298
301
  }
@@ -351,7 +354,7 @@ export class SessionStore extends EventEmitter {
351
354
  debounceTimer = null;
352
355
  persistPath;
353
356
  settingsFilePath;
354
- /** 网关运行时状态契约 */
357
+ /** 安全接入代理运行时状态契约 */
355
358
  gatewayStatus = {
356
359
  mode: 'direct',
357
360
  running: true,
@@ -390,7 +393,7 @@ export class SessionStore extends EventEmitter {
390
393
  getOptions() {
391
394
  return { ...this.options };
392
395
  }
393
- /** 设置网关当前生效的运行时参数与状态契约 */
396
+ /** 设置安全接入代理当前生效的运行时参数与状态契约 */
394
397
  setActiveGatewayInfo(mode, activePort, corePort, running = true, error = null) {
395
398
  this.gatewayStatus = {
396
399
  mode,
@@ -400,11 +403,11 @@ export class SessionStore extends EventEmitter {
400
403
  error,
401
404
  };
402
405
  }
403
- /** 获取网关完整运行状态对象 (供 /api/remote-mobile/status 消费) */
406
+ /** 获取安全接入代理完整运行状态对象 (供 /api/remote-mobile/status 消费) */
404
407
  getGatewayStatus() {
405
408
  return { ...this.gatewayStatus };
406
409
  }
407
- /** 获取当前实际对外提供访问的模式 (gateway | direct) */
410
+ /** 获取当前实际对外提供访问的模式 (proxy | direct) */
408
411
  getActiveMode() {
409
412
  return this.gatewayStatus.mode;
410
413
  }