Access the dsh web UI over LAN: HTTP/HTTPS/WS forwarding plus TLS (self-signed or custom certificates); adaptive Brotli/gzip compression for HTTP and permessage-deflate for WebSocket; half-open WebSocket probing keeps mobile clients from going stale after backgrounding; launch-token auto-injection lets LAN devices connect without copying the token; DNS-rebinding protection + loopback-only upstream allowlist.
Install
# from npm (prebuilt)
dsh plugin --profile web add @wingsky-1/dsh-lan-proxy
# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)
dsh plugin --profile web add github:wingsky-1/dsh-plugin-hub#path:/packages/dsh-lan-proxy
Any plugin you install runs third-party code with your own permissions — it can read your files, use your credentials, and reach the network, and tool approvals don’t sandbox it. GitHub-sourced plugins also run build scripts at install time — pnpm blocks those until you allow them, so an install can stop with ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED or ERR_PNPM_IGNORED_BUILDS; dsh prints the exact key to add under allowBuilds in your profile’s pnpm-workspace.yaml, and the install works on the next run. Allowing a build is a trust decision: only install sources you trust, and pin a commit (github:owner/repo#sha).
README
LAN access to the dsh web UI: listens on 0.0.0.0:<port> and forwards HTTP/HTTPS and
WebSocket/wss to the loopback web server (default 127.0.0.1:3080).
简体中文 | English
Quick install
dsh plugin --profile web add @wingsky-1/dsh-lan-proxy
After install / uninstall / update, restart
dsh webonce (bundle layers are only composed at startup) for changes to take effect.
Quick navigation
Before you start · Security Model · Quick start · Common configuration · Verification and troubleshooting · Detailed reference · Development and architecture
Before you start
Prerequisite: DeepSeek Harness installed and dsh web running normally (for running dsh
without a global install, see "Without a global dsh install" below).
- Rewrites Host/Origin to pass the /api browser trust perimeter
- Accepts only IP-literal or localhost Host headers (DNS rebinding protection)
- HTTPS runs by default alongside (3443); the certificate is configurable or auto-generated self-signed
Security Model
- Egress target allowlist (L1):
targetHostallows only loopback addresses (localhost / 127.0.0.1 / ::1), double-checked at both the config layer and the runtime entry — prevents open forwarding / SSRF - DNS rebinding protection: only requests whose Host is an IP literal or
localhostare accepted; domain names are always rejected with 403/disconnect; IP literals are safe on any port - Credential surface: dsh settings RPC is readable/writable on loopback only; remote devices reached through this plugin can read/write server settings (including credential-class data) within the browser trust perimeter — make sure your LAN is trusted, or disable this plugin
- Bridge header passthrough: the WS bridge forwards inbound headers (including
authentication cookies — since dsh 0.1.2
/api/remote.muxupgrades require cookie authentication, dropping them results in 401 and connection failure), overriding only Host/Origin to the loopback target and stripping hop-by-hop and WebSocket handshake-only headers; the upstream is forced to loopback bytargetHost, so credentials are sent only to the local loopback upstream (this does not promise cross-process isolation). Compression-bomb surface: the browser-segment permessage-deflate decompression is an amplification point (a hostile LAN client sending highly-compressed frames makes the proxy process decompress) — acceptable within the "LAN-trusted" threat model (same trust boundary asinjectToken) - Private key permission: auto-generated self-signed private keys are written with 0600; one-click CA private keys (CA + leaf) and .bak files are always 0600 (write + chmodSync double cover), and the certs/ directory is 0700
- Certificate serving surface (issue #911): the download route only serves public keys (loopback fence + GET only + no Cookie required). Only the first CERTIFICATE block is served; a mispointed private key always yields 404; responses are not cached. Without a CA nothing is served with 404 (self-signed/orphan leaves cannot establish trust once installed)
- One-click CA action surface (issue #930): POST-only writes (loopback fence + POST allowlist + requires a writable settings service). The CA private key filename is fixed (ca-key.pem) and never served; failure responses carry only fixed codes (ca-generate-failed, etc.) while paths and key material stay in server logs. Rotation replaces only the leaf by default (the CA is reused, installed devices keep working); rotating the CA is a separate dangerous action (trust on all installed devices breaks and each must reinstall). Superseded materials move to timestamped .bak files with only the most recent one kept
- Open port reminder:
0.0.0.0listening is visible to every device on the LAN - HTTP response compression: compression happens at the forwarding layer and only applies
to the link between this plugin and the LAN client; it never touches dsh web's response
generation, adds no reachable data surface, and only costs a small amount of CPU (disable
via
httpCompressEnabled: false). The loopback fence on health/marker routes applies to requests hitting the loopback web directly; requests forwarded through this plugin are trusted by design (see Credential surface). Diagnostic metadata in the health response — the absoluteconfigDirpath and compression negotiation counters — is forwarded unchanged and is visible to LAN devices
injectToken automatic injection (issue #380)
DSH browser-session authentication (launch token + persistent signed cookie) cannot be disabled.
The token changes on restart and is printed only in the local terminal, so fixed LAN devices cannot
obtain it themselves. Through the connection service’s public authenticatedUrl() API, the proxy
reads the current token dynamically and adds it only at the minting entry (GET / without a
session cookie), letting LAN devices enter without manual steps. Trade-offs and mitigations:
- Equivalent to trusting the entire LAN: any client that can reach this port gets full DSH control without a token, including bash access to the host. Enable only on trusted home/office networks; turn it off in the settings card on untrusted segments.
- On by default (maintainer decision): comparable precedents (Home Assistant’s
trusted_networksauthentication provider and qBittorrent WebUI’s "Bypass authentication for clients") require explicit configuration by default. This plugin defaults on for fixed home-LAN use, with warnings in the startup banner, settings card and this section as mitigation. - Turning it off does not revoke issued cookies: logged-in devices remain able to enter during the cookie lifetime (30 days by default). Clear the DSH credentials store to revoke access immediately.
- Invalid-cookie recovery: after a credentials reset, a browser may retain an invalid cookie until Max-Age and cannot obtain a new token, causing a 401 deadlock. On an upstream 401 the forwarder replays once with the token so upstream remints the cookie transparently.
- Residual cross-site surface: a hostile public page can trigger a cross-site GET to
http://<LAN-IP>:3081/and mint a cookie, but cannot read the response (CORS opaque); subsequent cross-site requests omit it underSameSite=Strict, and the sec-fetch-site fence rejectsPOST /api. Token and cookie never appear in the browser address bar or history. - Non-injection boundary: only
GET /without a token parameter is eligible. WebSocket, non-root paths, requests already carrying a token, and unavailable providers pass through unchanged. A password page (trust narrowed to those who know the password) is a future direction.
ownsHostCompat compatibility injection (issue #856, off by default)
through the official
webServer index injection hook, a self-conditioned script is injected into non-loopback
pages, declaring globalThis.__DSH_TRANSPORT__ = { ownsHost: true }. This forges an
upstream topology fact — served pages are not supposed to carry that global. It is not a
server-side authorization change: the /api fence, launch token and session-cookie auth are
unchanged; only the page-side fact is.
- Unlocked behaviours: (1) settings persistence moves from memory scope back to host scope
and is written to
<DSH_HOME>/settings.yaml(created withflag: "wx"when missing); (2) the host-native "open settings file" action (the settings page can ask the host to open that file). - Remote and local pages become indistinguishable in the UI:
isLoopbackis the only local/remote signal, and in compat mode a LAN page has the same value as a127.0.0.1page. - Injection bounds: the script element lands in every index.html served through this
plugin (loopback pages included), but the script is self-conditioned — it returns
immediately when the page already carries
__DSH_TRANSPORT__(compositions that bring their own transport, e.g. desktop-host, are untouched) or when the authority is loopback (localhost / [::1] / 127/8), writing neither transport nor marker. A loopback page receiving the script element and a loopback page being altered are two different things; the latter never happens. - How to turn it off: Plugin Manager → dsh-lan-proxy → Configure, disable "Declare ownsHost to
non-loopback pages (compat)" (composition-level config:
ownsHostCompat: false), then reload the page. - Zero-forgery alternative:
ssh -L 3080:127.0.0.1:3080 <host>and browsehttp://127.0.0.1:3080/— the page authority is already loopback, so settings persistence works without impersonating any topology fact. - Failure visibility: the verdict has four states (local page / compat active /
upstream contract drift / switch off). Neither fault state depends on the settings card, and
there are three visibility surfaces — but they do not cover the same ground:
(1) devtools console — on page load (at the very top of
apply) one[dsh-lan-proxy]warning is logged, once per page assembly (not on every render), so it never floods, and it does not depend on the settings surface being available; this is the only fault-state outlet that reflects the page-side fact (whether the injection actually took effect); (2) the startup banner'sownsHostCompat: ON/OFFline; (3) theownsHostCompatfield ofGET /api/dsh-lan-proxy/health. (2) and (3) only report the host-side switch — they cannot answer whether upstream has drifted. The settings card also shows a persistent four-state verdict line, but only while the card is mounted (see the next item). - Known limitation (both fault states are unreachable on the page): on the target dsh
0.1.7-rc.2, the card is registered throughconfigForms.whileServed(["dsh-lan-proxy"])and the keyedplugins.row.configslot. Its canonical row id / settings namespace isdsh-lan-proxy, and its row key is@wingsky-1/dsh-lan-proxy#dsh-lan-proxy. The row exists only while the Host serves that namespace; when a non-loopback page's settings surface is reduced to memory scope, the namespace is absent, so the row and card are not mounted. The crux is that the sameisLoopbacksignal decides both whether the card mounts and what the four-state verdict is, socontract-drift(upstream removed/renamed/reordered theownsHostpredicate and the injection is dead) andcompat-off(switch off) — precisely the two states that most need to be seen — have no carrier on the page. Those two states are therefore visible only through (1) the devtools warning (page-side drift) plus (2)/(3) the banner and health field (host-side switch). Changing the switch happens host-side only:dsh-lan-proxy.ownsHostCompatinsettings.yaml, thecordis.patch.ymlbase layer in a profile, or a loopback browser on the settings page.
Quick start
Installing opens ports: this plugin listens on
0.0.0.0:3081(HTTP) and0.0.0.0:3443(HTTPS), making your dsh web reachable by every device on the LAN. Uninstall it (see Uninstall plugins below) when not needed.
Install plugins (add)
dsh plugin --profile web add @wingsky-1/dsh-lan-proxy
After install / uninstall / update, restart
dsh webonce (bundle layers are only composed at startup) for changes to take effect.
Access
From a trusted LAN device, open https://<server-IP>:3443/ (or http://<server-IP>:3081/). A self-signed certificate requires a one-time manual "proceed"; see HTTPS Support.
Verify
From the host, check the health endpoint:
curl -s http://127.0.0.1:3081/api/dsh-lan-proxy/health
Configuration
| Key | Default | Description |
|---|---|---|
enabled |
true |
Master switch (off stops the forwarder listeners) |
host |
0.0.0.0 |
Listen address |
port |
3081 |
HTTP listen port |
httpsPort |
3443 |
HTTPS listen port |
targetHost |
127.0.0.1 |
Loopback upstream host (loopback addresses only) |
targetPort |
auto | Upstream port (defaults to the web server's actual bound port) |
httpsEnabled |
true |
Whether to run HTTPS alongside |
tlsCertFile / tlsKeyFile |
none | Custom certificate (mkcert, etc.) |
tlsCaCertFile |
none | Self-built LAN CA public key (PEM, read-only) |
printBanner |
true |
Print the startup banner with LAN access URLs |
wsBridgeEnabled |
true |
Bridge switch; disabling it drops keep-alive and compression. |
wsCompressEnabled |
true |
Whether to apply compressed bridging to WebSockets matching wsCompressPaths (compression only; does not affect bridge keep-alive) |
wsCompressPaths |
/api/remote.mux |
Path allowlist participating in WebSocket compression (empty = bridged without compression, keep-alive unaffected) |
wsDeflatePolicy |
{browser:true, uaDeny:[iPhone…]} |
Browser compression policy and UA exclusions. |
httpCompressEnabled |
true |
Forwarding-layer gzip/Brotli switch. |
httpCompressLevel |
1 |
Compression preset 0..3 for gzip and Brotli. |
injectToken |
true |
Token-free LAN access; on by default. See Security Model. |
ownsHostCompat |
false |
Forge the page-side host fact; off by default. See Security Model. |
Configuration page: Plugin Manager → dsh-lan-proxy → Configure (saved changes apply hot).
Non-loopback LAN origins intentionally do not expose a persistent settings surface, so the Configure control is hidden by DSH's security policy. Manage settings from
127.0.0.1:3080/127.0.0.1:3081or an SSH loopback tunnel. EnableownsHostCompatonly on a trusted LAN and only when you accept the shared control-plane risk.
Configuration storage (single channel)
- All configuration lives in the dsh official settings store (the
dsh-lan-proxycanonical namespace); the host owns persistence. The composition-layercordis.patch.ymlconfig acts as the base layer. Hot reload is driven by the officialsettings/document-updatedevent — no restart needed.
Settings namespace
The DSH importer consumes the same-id dsh-lan-proxy section directly; this plugin does not run a second namespace migration. settings.yaml.imported is an audit copy of the consumed document and must not be copied wholesale.
- This plugin no longer maintains its own
~/.dsh/lan-proxy/config.json. On the first start after upgrading, a legacy config.json is migrated once into the official store: the original file is atomically renamed and kept asconfig.json.migrated.bak. If persisting into the official store fails, the rename is rolled back and retried on next start; if the process exits before the write completes (interrupted), the leftover backup is detected on next start and its content is replayed into the store, with a log note. Delete that backup manually once everything works. Editing config.json afterwards has no effect.
Configuration details
wsBridgeEnabled
WebSocket bridge master switch (issue #552): true = all WS upgrades go through "termination + bridge" (keep-alive base: auto-answers upstream Pings + half-open probes); false = TCP byte passthrough (explicitly drops keep-alive and compression — mobile backgrounding can be killed by upstream heartbeat and cause frequent reconnect loops, see "WebSocket Bridge & Compression")
wsDeflatePolicy
Compression negotiation policy: browser: false disables compression globally; uaDeny lists UA fragments denied compression (iOS Safari is denied by default)
httpCompressEnabled
Master switch for HTTP response compression (the forwarding layer negotiates gzip/Brotli for compressible responses; Brotli's effective condition is documented in "HTTP Response Compression", merged from dsh-gzip)
httpCompressLevel
Compression preset 0..3: 0 default / 1 low (gzip 1 / br 2, fastest) · 2 medium (gzip 5 / br 5, balanced) / 3 high (gzip 9 / br 9, best ratio) — the gzip and Brotli parameters are both passed down; legacy integer values 4..9 are migrated to 3 automatically
injectToken
Auto-inject the current launch token on the first GET / to mint a session cookie; replay once after an upstream 401 to recover an invalid cookie (issue #380). See Security Model
ownsHostCompat
Declare ownsHost to non-loopback pages (issue #856): forges an upstream topology fact, unlocking settings persistence to <DSH_HOME>/settings.yaml and the host-native "open settings file" action; remote and local pages become indistinguishable in the UI. Off by default — trade-offs, how to disable it and the ssh -L zero-forgery alternative are in the Security Model
Verification and troubleshooting
# Health check (loopback; includes compression config, active state and negotiation counters)
curl -s http://127.0.0.1:3081/api/dsh-lan-proxy/health
# Merged-compression marker route (loopback)
curl -s http://127.0.0.1:3081/api/dsh-lan-proxy/compression
# LAN access (from another device)
curl http://<your-LAN-IP>:3081/api/dsh-lan-proxy/health
Known Limitations
- HTTPS self-signed certificates are generated by a built-in library with no external command dependency (when unavailable and no certificate file is configured, the HTTPS channel auto-degrades and shuts down)
- When changing network segments causes the IP to change, the self-signed certificate must be regenerated or the settings (certificate file paths) updated
- WS bridging applies to all WebSockets by default (keep-alive base); compression only applies
to paths matching
wsCompressPaths(one extra hop, extra compression CPU). Bridging is a parsing termination proxy: subprotocol and close codes are not preserved across ends (the dsh client does not depend on either — verified zero impact) - With
wsBridgeEnabled=falseall WebSockets go passthrough (saves one hop of CPU), but mobile backgrounding >4~6s gets killed by the upstream heartbeat and causes frequent reconnect loops — only recommended for setups with no mobile clients
Detailed reference
WebSocket Bridge & Compression (wss event stream)
- Bridging is the default base (issue #552): while
wsBridgeEnabledis on (default), all WebSocket upgrades go through "termination + bridge" — lan-proxy speaks ws on both the browser segment and the DSH segment and forwards frames both ways. Bridging provides two capabilities that are independent of compression:- Automatic upstream Ping reply: the dsh upstream (api-gateway) sends one WS Ping on
/api/remote.muxevery 2s and callsterminate()after 2 cycles (46s) without a Pong. The bridge's upstream connection auto-replies Pongs via the ws library — mobile backgrounding / screen-off / brief freezes no longer trip the upstream kill; the connection survives until the device returns (no more "disconnect → reconnect" loops). - Half-open liveness probe (Refs #268): the bridge pings the browser segment and the DSH
segment independently every 30s; a ping with no pong by the next cycle (
3060s of silence) marks that side half-open and terminates it. A termination logslan-proxy: ws-bridge half-open detected, terminating (intervalMs=...)at warn level.
- Automatic upstream Ping reply: the dsh upstream (api-gateway) sends one WS Ping on
- Compression is an optional enhancement on top of the bridge: when a path matches
wsCompressPaths(default/api/remote.mux— the Remote-stream mux endpoint owned by api-gateway since dsh 0.1.2, replacing the old/api/events.mux,/api/events.host) andwsCompressEnabledis on, the browser segment negotiates permessage-deflate (the browser decompresses automatically) while the DSH segment stays plaintext, then both directions are bridged and forwarded. Benefit: remote.mux carries heavy real-time frames — permessage-deflate measures roughly 75~79% savings in practice. - Clearing the compression allowlist / turning compression off no longer drops keep-alive
(issue #552):
wsCompressPaths=[]orwsCompressEnabled=falseonly disables compression; the bridge (Pong reply + probes) stays active. - Even if the DSH server later enables permessage-deflate itself, the DSH segment here never negotiates compression; the two segments are independent, so there is no double compression and no conflict.
- Bridging is not byte-transparent: it terminates and re-originates the WS connection, so subprotocol (Sec-WebSocket-Protocol) negotiation and close codes are not preserved across ends (the dsh client currently depends on neither — verified zero impact); treat this as the contract for generic/future endpoints.
- Explicitly disabling the bridge (
wsBridgeEnabled=false): all WS goes through TCP byte passthrough (saves one hop of CPU), but loses Pong reply and probes — mobile backgrounding4~6s gets killed by the upstream heartbeat and causes frequent reconnects; only recommended for setups with no mobile clients.
HTTP Response Compression (Brotli/gzip, merged from dsh-gzip)
Since v0.1.10, the HTTP response compression capability of the standalone dsh-gzip plugin (source removed from this repository) has been merged into this plugin, implemented at the forwarding layer via the battle-tested compression middleware (inlined at build time): for requests served through this plugin, compressible responses (JSON / text) from
/api(RPC),/plugins(client bundles), and static assets/index.html negotiate compression automatically; SSE (text/event-stream), zip exports, already-encoded responses, HEAD, Range requests, and responses under 1KB pass through untouched.When Brotli actually applies (measured): dsh's own web server already ships gzip compression (
compression: gzip) and negotiates gzip only. Two cases therefore exist on this path:Client Accept-EncodingUpstream Final response through this plugin br, gzip(mainstream browsers)gzip gzip (already encoded, this layer defers instead of re-compressing) gzipgzip gzip (same) br(br only)raw br In other words, this layer's Brotli applies only when the upstream left the response uncompressed and the client declared br alone. Mainstream browsers declare gzip as well, so what arrives is the upstream gzip and the extra Brotli ratio is not realized on this path; that case is still far better than no compression (the same response measured 322900 → 5065 bytes). The two layers never double-compress.
Benefit: large JSON responses such as session history (4~13MB uncompressed) often hit the browser RPC 30s timeout over remote/slow links ("history load failed"); after compression they are ~1.2MB — measured in an isolated environment at ~36s down to ~3s.
The middleware sits on the forwarder's own listener chain and does not modify dsh web or any other plugin's runtime behavior; set
httpCompressEnabled: falseto turn this layer's compression off (when the client also accepts gzip, the upstream's own gzip still compresses such responses, so the switch does not change their on-wire size). Note: traffic that reaches the loopback web directly (local browser on127.0.0.1:3080, not through this plugin) is outside the compression surface — loopback links do not need compression.Migrating from dsh-gzip: upgrade this plugin, confirm compression is active, then uninstall the standalone gzip package:
dsh plugin --profile web update @wingsky-1/dsh-lan-proxy # requires >= 0.1.10 curl -s http://127.0.0.1:3081/api/dsh-lan-proxy/health # loopback check: continue when httpCompressMounted is true dsh plugin --profile web remove @wingsky-1/dsh-gzip # Restart dsh web to take effectWhen legacy gzip@0.1.9 (no detection logic) coexists with this plugin, the content-encoding check still guarantees responses are compressed at most once (verified for every assembly order) — responses are never corrupted; uninstall it promptly to keep health diagnostics unambiguous.
HTTPS Support
- Certificate sources (two tiers): ① configure
tlsCertFile/tlsKeyFile(official certificate or mkcert local CA, zero browser warnings); ② auto-generate a self-signed certificate (built-inselfsignedlibrary generates and caches it to<DSH_HOME>/@wingsky-1/dsh-lan-proxy/(legacy lan-proxy dir is migrated on first boot), private key permission 0600, no host openssl required) - The self-signed certificate needs a one-time manual "proceed" on first visit; for zero warnings on LAN devices, a self-built LAN CA is recommended (install once per device, see below)
- One-click local CA (issue #930): on the settings page, the LAN access card offers "Generate local CA", issuing a 10-year CA plus a 398-day leaf into the managed directory
<DSH_HOME>/@wingsky-1/dsh-lan-proxy/certs/(CA public keyca-cert.pem/ private keyca-key.pem/ leafleaf-cert.pem+leaf-key.pem) with the three keys filled back automatically. "Rotate leaf certificate" only replaces the leaf by default (the CA is unchanged, installed devices keep working); "Rotate CA" is dangerous. As a zero-code alternative, mkcert works the same way (same hint as above) - Install certificates on mobile devices (issue #911; #930 Phase 1: no CA, no download): after configuring tlsCaCertFile (CA public key), the settings-page download link serves the CA (open the link directly in a browser). Without a CA the download link always yields 404 (neither the self-signed mode nor a custom orphan leaf is served: installing them cannot establish trust) — generate a local CA from the settings page first, or configure a CA public key and retry. iPhone: install the profile then enable trust in Certificate Trust Settings; Android: install the CA certificate under Security settings; Windows: double-click into Trusted Root Certification Authorities
Uninstall plugins (remove)
dsh plugin --profile web remove @wingsky-1/dsh-lan-proxy
Update plugins (update)
dsh plugin --profile web update @wingsky-1/dsh-lan-proxy
After install / uninstall / update, restart
dsh webonce (bundle layers are only composed at startup) for changes to take effect.
Pin a version (@version)
Omitting @version installs the default latest (recommended). Only when the registry has not synced the latest yet, or the latest has issues in your environment, append @version to the package name:
dsh plugin --profile web add @wingsky-1/dsh-lan-proxy@<version>
Without a global dsh install
If there is no global dsh command on the machine, use npx to run it on the fly (dsh plugin
calls pnpm under the hood, so pnpm and Node.js must still be installed locally):
npx @deepseek-ai/dsh plugin --profile web add @wingsky-1/dsh-lan-proxy
npx @deepseek-ai/dsh plugin --profile web remove @wingsky-1/dsh-lan-proxy
npx @deepseek-ai/dsh plugin --profile web update @wingsky-1/dsh-lan-proxy
Development and architecture
For architecture and runtime mechanisms, see the TOGAF 4A architecture document (in Chinese): Business, Application, Data, and Technology views.
Tests are maintained by layer under test/{unit,integration,e2e,client}/. Unit and integration tests import src/** directly; e2e smoke tests run the lib/ artifact (import "../../lib/index.js"). Stryker reuses the assertions via the lib→src hook, without hand-synced copies.
Configuration contract appendix
| Key | Host default | Client display default | Patch declaration |
|---|---|---|---|
port |
3081 |
3081 |
Not declared |
httpsPort |
3443 |
3443 |
Not declared |
host / targetHost |
"0.0.0.0" / "127.0.0.1" |
No such key | Not declared |
targetPort |
No default; follows the loopback web server actual port | No such key | Not declared |
injectToken |
true |
true |
Not declared |
ownsHostCompat |
false |
false |
Not declared |
enabled / httpsEnabled / printBanner / wsBridgeEnabled / wsCompressEnabled / httpCompressEnabled |
true |
true |
Not declared |
httpCompressLevel |
1 (0-3) |
1 |
Not declared |
wsCompressPaths / wsDeflatePolicy / tlsCertFile / tlsKeyFile |
["/api/remote.mux"] / {browser:true, uaDeny:[iPhone,iPad,iPod]} / no default |
["/api/remote.mux"] / no such key / "" |
Not declared |
Host defaults come from DEFAULT_OPTIONS in src/server/shared/defaults.ts and DEFAULT_DEFLATE_POLICY in src/server/shared/deflate.ts, applied via Config / DEFAULT_CONFIG in src/server/config/impl/model.ts; client defaults come from DEFAULTS in src/client/shared/defaults.ts; cordis.patch.yml (dsh-lan-proxy) carries no config on either the standalone or aggregate row. injectToken on is equivalent to trusting the whole LAN, and ownsHostCompat on declares ownsHost to non-loopback pages; see "Security Model" for details. The code above is the single source of truth; where docs and code disagree, the code prevails.
License
MIT
Links
More in this category
zhu1090093659/dsh-web#packages/dsh-remote-web-ui★ 8262
Remote control of a dsh web workspace from phone or PC: QR-code pairing through a token-gated channel, SSE real-time sync, and separate mobile and full desktop GUI modes.
zhu1090093659/dsh-web#packages/dsh-ssh★ 8262
SSH ops panel for DSH: web terminal, SFTP transfer with progress, local port forwarding, and one-command cluster execution across hosts; agents share the same host config.
saya-ch/dsh-mobile★ 350
Access DeepSeek Harness from the Android app or a mobile browser with secure LAN and remote connections, persistent device pairing, and a customizable mobile interface.
ZSeven-W/dsh-ios★ 310
A live iOS Simulator or USB-connected iPhone inside the conversation: 22 agent tools for booting, building, driving the UI by accessibility identity or OCR text, list-row actions and SwiftUI preview hot reload, plus a streaming sidebar panel you can tap and drag on.
liguobao/ds-harness-remote★ 248
Multi-device remote access for DeepSeek Harness: continue an active session from your phone, tablet, browser, or another computer over an end-to-end encrypted channel (Noise IK + adaptive relay/WebRTC transport), with device authorization, ApiProxy-only remote capabilities, and read-only file preview via dsh-file-viewer — no shell, remote desktop, or write access.
wenbin-wb/dsh-bridge★ 179
Remote and mobile access for DeepSeek Harness: provides LAN QR code connection, Cloudflare/custom tunnels, WeChat, QQ, Feishu, Telegram bot integration, and security authentication.
Community comments
Comments are public GitHub Discussions. Loading them connects to GitHub and Giscus; a GitHub account is required to post.