AcidGr/dsh-web-lan-access
dsh-web-lan-access
DeepSeek Harness (dsh) Web plugin
Install
npx @deepseek-ai/dsh plugin --profile web add dsh-web-lan-accessRestart `dsh web` after install. Bundle APIs can change during the developer preview.
README badge
[](https://dshhub.dev/plugins/dsh-web-lan-access)Paste this into your README. The star count updates with every catalog sync.
From the README
Excerpt from AcidGr/dsh-web-lan-access, cleaned of badges and images.
dsh-web-lan-access
English | 简体中文
LAN / remote access support for the DeepSeek Harness Web UI.
Bind every interface with one command, reach the GUI from a LAN IP, a Tailscale IP, or a VPN address, and keep Host-backed settings working on the remote page.
Compatibility
| dsh | plugin | notes |
|---|---|---|
| 0.2.1-rc.2 | 1.4.x | current; verified end to end |
| 0.2.0-rc.x | 1.3.x | shipped webserver still accepted 0.0.0.0 |
| ≤ 0.1.7 | 1.3.x | no wildcard ban yet |
Version 1.4.0 is the release that tracks dsh 0.2.1's stricter webserver. See Why 1.4.0 had to change if you are upgrading from 1.3.x.
The problem
Three separate things stand between a remote browser and a working GUI:
1. The listener binds loopback only. dsh web defaults to 127.0.0.1, and from 0.2.1 the CLI rejects a wildcard host outright:
error: --host 0.0.0.0 is an unspecified (wildcard) address, which is not supported:
binding every interface would expose remote code execution to the network
2. The /api fence rejects unknown authorities. Every request must name a loopback host, the listener's own bind literal, or a declared trustedHosts entry. A wildcard bind has no literal of its own — 0.0.0.0 matches no request Host — so the page shell loads but every /api call returns 403 and sessions never appear.
3. Host-backed settings are gated on isLoopback. client-connection derives isLoopback: transport?.ownsHost === true, and client plugins consult that fact before reaching Host surfaces. A page served from a LAN or Tailscale address is not loopback by hostname, so the Models and plugin settings pages stay empty even after authentication succeeds.
The fix
Two small pieces, both reversible.
A replacement webserver row
lib/webserver.js subclasses @deepseek-ai/dsh-host-webserver and changes exactly two things:
Configdelegates to the shipped schema and rescues only the wildcard rejection. A non-literal host, a port out of range, or a missing port still fail exactly as before — the relaxation is one error message wide.getOrigin()never returns a wildcard origin. The parent builds its URL from the bind address, which would yieldhttp://0.0.0.0:3080— an address no browser can dial. A wildcard listener reports loopback instead, and--public-urlstill overrides the advertised root upstream.
Everything else — the service name, routes, index taps, TLS handling, gzip, listener lifecycle — is inherited untouched.
A /api fence widened from the live interface list
The bundle patch derives trustedHosts from every non-internal IPv4 the host currently has, so LAN, Tailscale (100.x), and VPN addresses are all accepted without per-IP configuration.
A bootstrap script for Host ownership
Injected as the first script in <head>, before the boot manifest and shell entry, through the webserver's official index-tap extension point (webServer.tapIndex). It declares ownsHost: true while keeping the ordinary HTTP fetch transport, and leaves a transport supplied by another shell untouched.
…
