dsh-lanmode
goodandready/dsh-lanmode
为 DeepSeek Harness 网页 UI 提供局域网与反向代理访问:在非 localhost 页面返回设置服务,补齐浏览器在纯 HTTP 下限制的 Web API,并可自行开启监听端口。
安装
dsh plugin --profile web add github:goodandready/dsh-lanmodeREADME
📦 @goodandready/dsh-lanmode
Local Area Network (LAN) Access Enabler, mDNS (dsh.local), PWA, Root CA, QR Code, Background Notifications & Auto-TLS for DeepSeek Harness
🇬🇧 English • 🇷🇺 Русский • 🇨🇳 中文说明
|
⭐ If you like this plugin, please star it on GitHub — it shows me that the plugin is useful to you and motivates me to keep developing it.
🐛 If you find a bug or would like to request a feature, open a GitHub issue in any language — I will review your proposal and implement useful suggestions in a future plugin version. |
⚡ Why DSH Fails Over Local Network (LAN)
By default, modern web browsers and the DeepSeek Harness frontend deliberately restrict access when opened from non-localhost IP addresses (e.g. 192.168.x.x or 10.x.x.x) over plain HTTP:
- 🔒 Locked Settings & Models Tabs: The Web UI evaluates the hostname via
isLoopbackHostname. If accessed over LAN, the settings service falls back to in-memory mode: all plugin configuration cards render empty, section states become"unavailable", mutations are discarded before transmission, and the Models page displays "settings are unavailable in this browser". - 💥 Fatal UUID Generation Crash:
crypto.randomUUID()only exists in browser Secure Contexts (HTTPS or localhost). On plain HTTP across LAN, file uploads, tool calls, and session initializations crash instantly. - 📋 Broken Clipboard Copying:
navigator.clipboardis completely disabled by browsers on non-secure origins, breaking all code snippet "Copy" buttons. - 🎙️ Microphone & Voice Input Blockade: Browser security engines block
navigator.mediaDevices.getUserMediaon plain HTTP, making voice input viadsh-voiceimpossible on remote mobile phones and tablets. - 🛡️ Loopback-Only Core API Fencing: Core DSH methods (
/api/settings.*,/api/credentials.*,/api/models.*) strictly reject requests not originating from loopback127.0.0.1.
dsh-lanmode completely resolves all these limitations through non-invasive webServer.tapIndex HTML shims, a smart direct bridge, mDNS, Root CA generation, and an interactive settings card.
graph LR
subgraph RemoteDevices [LAN Clients: Phone / Tablet / Laptop]
Client[📱 Mobile Safari / 💻 Laptop: dsh.local:3088] -->|mDNS & HTTPS| Bridge[dsh-lanmode Smart Direct Bridge]
end
subgraph ShimsLayer [tapIndex Injected Client Shims & PWA]
Bridge --> Shim1[🔓 Loopback Hostname Bypass: Unlocks Settings & Models]
Bridge --> Shim2[🆔 RFC 4122 crypto.randomUUID Polyfill]
Bridge --> Shim3[📋 Fallback navigator.clipboard Polyfill]
Bridge --> Shim4[🔐 Local Root CA & TLS: Unlocks WebRTC Microphone]
Bridge --> Shim5[📱 PWA Manifest & Safe-Area Viewport]
Bridge --> Shim6[🔔 Background Web Notifications on turn/end]
end
subgraph HostBackend [DSH Host Core]
Bridge --> HeaderRewrite[Loopback Host/Origin Header Rewriter]
HeaderRewrite --> PrivilegedAPI[Core Settings, Credentials & Models API]
end
subgraph Output [Result]
Shim1 --> FullWeb[✅ 100% Fully Functional Web UI Across Entire LAN]
Shim2 --> FullWeb
Shim3 --> FullWeb
Shim4 --> FullWeb
Shim5 --> FullWeb
Shim6 --> FullWeb
PrivilegedAPI --> FullWeb
end
style RemoteDevices fill:#1e1e2e,stroke:#89b4fa,stroke-width:2px,color:#cdd6f4
style ShimsLayer fill:#181825,stroke:#cba6f7,stroke-width:2px,color:#cdd6f4
style HostBackend fill:#11111b,stroke:#a6e3a1,stroke-width:2px,color:#cdd6f4
style Output fill:#181825,stroke:#f38ba8,stroke-width:2px,color:#cdd6f4
✨ Full Feature Breakdown
1. 📱 Quick QR Popover, /mobileqr Command & Mobile Pairing
- Quick QR Access: Dedicated phone icon in the sidebar footer (
sidebar.footer/sidebar.rail) opens an interactive popover with local vector SVG QR code, instant LAN / WAN switch, and one-click URL copying. - Terminal & Agent Access: Registers tool
/mobileqrin agent chat for instant QR generation, and outputs an ASCII QR code to terminal stdout ondsh webstartup. - Diagnostic & Health: Clean SVG QR code is also accessible via
/dsh-lanmode/qrand on/dsh-lanmode/health. Point your phone camera at the screen to connect immediately.
2. 📲 PWA & Mobile Standalone Mode
- Route
/dsh-lanmode/manifest.jsonand meta tagsviewport-fit=cover,apple-mobile-web-app-capable,theme-color. - Mobile layout styles are injected with
data-dsh-plugin="dsh-lanmode", so the harness can tell them apart from other plugins. - Adding DSH to your Home Screen on iOS/Android launches it as a standalone app without browser URL bars and with notch-aware safe areas.
GET /dsh-lanmode/manifest.webmanifestis public, so the home-screen install does not depend on a session.- A saved launch token can reopen that bookmark. Clearing site data drops the token.
- Scan
/dsh-lanmode/pair-accept?token=to pair a phone without setting a cookie. Empty tokens and tokens longer than 512 characters are rejected. - On a phone, the QR action sits on the same footer row as settings. A long press on a session row opens its menu. The model menu stays pinned to the bottom of the screen. Wide desktop panels close on a narrow screen; other plugins remain visible.
- Chat links and file opens whose path ends in zip, exe, dmg, pkg, msi, 7z, rar, gz, bz2, iso, bin, or apk download instead of opening in the page.
3. 🌐 Automatic mDNS (dsh.local)
- Built-in lightweight UDP 5353 responder: announces
dsh.localacross your local network. No need to memorize shifting IP addresses.
4. 🔐 Local Root CA for Permanent Trusted HTTPS
- Generates a two-tier certificate structure:
dsh-lanmode Local Root CA(10-year validity) $\rightarrow$Server Certificate(with SAN fordsh.local, LAN IPs, and localhost). - Download
GET /dsh-lanmode/ca.crt: install the profile once on your iPhone, iPad, or Android to enjoy persistent trusted HTTPS. Voice input viadsh-voiceworks flawlessly. - The saved certificate is reused across restarts. A newly issued certificate also lists sslip.io and nip.io names for each address.
- Those names belong on the certificate only. The bridge does not listen on sslip.io or nip.io host names.
tlsSitesadds extra certificate and key files for specific host names. A request for that name uses its own certificate. IP addresses, localhost, and unknown names stay on the default certificate. An empty list does not enable name selection.
5. 🔔 Background Web Notifications (turn/end)
- Hooks into
turn/endandapproval/askedsession events. - When the tab or phone is inactive (
document.hidden), dispatches a native push notification. Tapping the notification immediately refocuses the chat window.
6. 🎨 Settings Card in «Settings → Plugins» (lib/client.js)
- Interactive plugin card following DSH design guidelines:
- Connection status & active mode;
- One-click LAN URL copying;
- In-card QR code toggle;
- One-click background notification toggle;
- Download Root CA link (
ca.crt).
7. 🛡️ Access Control, LAN PIN & Security
unlockPrivileged: Master gate for settings & credentials mutation from LAN.lanPin/lanPinRef: Optional PIN protection for privileged operations. When enabled, LAN guests can chat freely, but changing system settings, installing plugins, or mutating credentials requires PIN verification.- Brute-Force Rate Limiting: PIN authentication enforces automatic rate limiting (HTTP 429 status after 5 consecutive failed attempts per IP) with temporary lockout.
- Subnet Role Separation: Distinct
adminAllowandguestAllowCIDR rules. Subnets designated underguestAlloware strictly prohibited from mutating system settings, revoking sessions, or toggling WAN tunnels (403 Forbidden). - Administrative & Diagnostic Endpoints Protection: Internal plugin routes (
/dsh-lanmode/devices,/dsh-lanmode/devices/revoke,/dsh-lanmode/devices/kill-all,/dsh-lanmode/tunnel/toggle,/dsh-lanmode/api/interfaces,/dsh-lanmode/api/telemetry,/dsh-lanmode/api/config) feature built-in fail-closed defense-in-depth authorization. Bypassing the local bridge or accessing from untrusted networks requires valid admin credentials or trusted loopback origins. - Login, settings, device revoke, and tunnel toggle stop reading a body after 64 KiB and answer 413. The action is not applied.
- CSRF Mitigation: Mutating POST requests reject cross-site invocations (
Sec-Fetch-Site: cross-site) and validate origin headers. - Password Storage & Verification: Passwords support both plaintext strings (for configuration compatibility) and robust scrypt digests (
scrypt$16384$8$1$salt$hash) generated viahashAuthPassword(). Credential verification runs in constant time (timingSafeEqual), with unknown usernames incurring an identical dummy scrypt cost to equalize response latency against user enumeration. Changing the password revokes other active sessions for that user immediately. - LAN PIN Protection: The LAN PIN supports both plaintext PIN strings and PBKDF2 digests (
pbkdf2$sha512$100000$salt$hash) generated viahashPin(). Brute-force rate limiting enforces a 15-minute temporary lockout for that IP address after 5 consecutive failed attempts. - Session & Device Tokens: Active sessions are validated via SHA-256 digests (
hashToken), ensuring token secrets are not exposed as plaintext in serialized storage files (dsh-lanmode-devices.json). - Set the first password, password reference, or
passwordAuth: truefrom the machine itself. A remote address receives 403 and the configuration is left unchanged. - While password authentication stays on, a settings update cannot clear both the password and the password reference. Turning password authentication off is still allowed.
POST /dsh-lanmode/banswith{ "ip" }bans an address. That address then receives plain403 Forbiddenbefore the login page. Loopback and the administrator's own address cannot be banned. The list is kept beside the device registry.disabledUsersnames accounts whose sessions are dropped within 5 seconds.- Forwarded client addresses (
X-Forwarded-For,CF-Connecting-IP) are trusted only from peers listed intrustedProxyCidrs. Direct peers cannot spoof their address. - If a signed-in API call returns 401 with
x-dsh-auth-required: 1, the page asks for the password again without navigating away, so the current draft stays. - The login card shows the host name you are signing into.
8. 📱 Connected Devices & Session Management
- Live client presence tracking and device OS/browser discovery (iOS, Android, Windows, macOS, Linux).
- Per-device token revocation and emergency "Revoke All Others" kill switch in the settings card.
- Bridge routes that list or revoke devices require administrator access. Guests receive 403.
- Each device row can show a short name taken from the User-Agent.
- If the device list, tunnel status, update check, or latency request fails, the card shows that failure instead of an empty success.
9. 🌐 Multi-Interface & Mesh Detection
- Automatic identification of local LAN, Tailscale (100.x.y.z), WireGuard, and VPN network adapters with quick-select UI pills.
- Automated firewall management for Windows Defender Firewall, Linux UFW, and firewalld.
10. ⚡ Live Network Telemetry & HTTP/2 ALPN
- Compact real-time telemetry widget displaying RTT ping latency, active concurrent connections, and streaming data volume.
- Native HTTP/2 (ALPN
h2) bridge support alongside HTTP/1.1 for multiplexed low-latency streaming.
11. 🚀 Connection Pooling & SSE Streaming Isolation
- Upstream connections to DeepSeek Harness are segregated into two independent pools:
- Standard HTTP Pool: Keep-alive enabled with up to 100 reusable sockets for rapid loading of WebUI assets, static scripts, and REST endpoints. Protected by a queue timeout (15s default) returning HTTP 503 rather than stalling indefinitely if saturated.
- Dedicated Streaming Pool: Independent unpooled socket handling for long-lived Server-Sent Events (SSE), token streaming (
/api/chat/stream), and live notifications. 100+ concurrent streaming clients can run without exhausting or starving WebUI static and API traffic. Response bodies are piped through; they are not buffered into one blob.
- Local LAN clients skip gzip and brotli. Remote clients can still receive compressed responses when
adaptiveCompressionis on (the default). - If the harness port is not configured, the bridge probes
127.0.0.1on 3080, then 3081, then 3082. An explicit port is used as given.
12. ☁️ Cloudflare WAN Tunnels & Tunnel PIN
- Zero-Config Quick Tunnels & Persistent Named Tunnels: Remote WAN access via Cloudflare without port forwarding or public static IP. Supports temporary Quick Tunnels (
trycloudflare.com) and persistent Named Tunnels configured withtunnelTokenortunnelTokenRef, complete with fast readiness detection from connection logs. - Mandatory Tunnel PIN Guard: Inbound requests through Cloudflare WAN tunnels can be required to pass the LAN PIN challenge (
tunnelPin: true, enabled by default) before gaining access.
13. 🔄 In-App One-Click Plugin Updates
- Built-in updater service and settings card UI (
/api/dsh-lanmode/update):- Real-time display of the currently installed version and availability of new releases from the npm registry;
- Security perimeter: mandatory
x-dsh-plugin-update: 1header, same-origin check, and the admin gate. Password authentication requires a valid session even from loopback. Without it, loopback or an admin-role address is accepted. Guests are rejected; - One-click upgrade of
@goodandready/dsh-lanmodedirectly from the DSH settings card with zero terminal commands required.
📦 Quick Installation
dsh plugin --profile web add @goodandready/dsh-lanmode
⚙️ Configuration Reference (profile cordis.patch.yml)
Since v0.8.0 (DSH 0.1.7+), all configuration lives in the profile row config: section.
Edit your profile's cordis.patch.yml to override defaults:
# ~/.dsh/profiles/web/cordis.patch.yml
- id: dsh-lanmode
config:
mode: direct # 'direct', 'proxy', or 'auto'
directHost: 0.0.0.0 # Default: 127.0.0.1 (localhost only)
directPort: 3080
mdns: true # Announce dsh.local in LAN
pwa: true # PWA manifest, splash screen & mobile viewport
mobileEnterSends: false # When false (default), Enter adds newline on mobile touch
tls: self-signed # 'self-signed' (with Root CA), 'files', or 'off'
unlockPrivileged: true # Permit settings & credentials from LAN
lanPinRef: "" # Credential reference name or ENV var for LAN PIN
tunnel: off # Cloudflare WAN tunnel: 'off', 'quick', or 'named'
tunnelTokenRef: "" # Credential reference name or ENV var for tunnel token
tunnelPin: true # Require PIN for requests from WAN
allow: # Default: ['127.0.0.0/8'] (loopback only)
- 192.168.0.0/16
- 10.0.0.0/8
passwordAuth: false # Require a username and password
authPasswordRef: "" # Credential name for the password; do not put the password here
publicHost: "" # Host name shown on the login card
disabledUsers: [] # Usernames whose sessions are revoked
trustedProxyCidrs: [] # Peers allowed to set X-Forwarded-For
tlsSites: [] # Extra {host, cert, key} certificates by server name
adaptiveCompression: true
📄 License
MIT © GooDAnDReaDY
🛡️ Security Note for Downstream Plugins
When dsh-lanmode is enabled, HTTP and WebSocket requests arriving at harness and neighboring plugin endpoints are proxied through loopback (127.0.0.1). The bridge passes standard proxy headers:
X-Forwarded-For: the actual remote client IP address on the local networkX-Forwarded-Proto:httporhttps
Important: Plugins must not consider a loopback connection (req.socket.remoteAddress === '127.0.0.1') as definitive proof of local console access if dsh-lanmode is active. Instead, inspect X-Forwarded-For or use proper role/session verification.