dsh-lan-pair: Securely open DSH web interface to LAN

Introduction

DeepSeek Harness (DSH)’s dsh web listens only on 127.0.0.1 for security reasons and explicitly rejects --host 0.0.0.0. Its session cookie has SameSite=Strict, so a mobile browser opening it via an external link will only receive HTTP 401 dsh web authentication required. If you want to control DSH with a tablet or phone in the same room, the native way won’t work.

dsh-lan-pair is maintained by FORGE-131. It consists of a DSH plugin and a 45 KB Android shell (named DSH LAN). The plugin opens a separate LAN port inside the DSH process and holds the DSH session cookie itself; the phone only talks to the plugin and never gets the DSH startup token. Below is an introduction to its installation, pairing, and remote access.

Its Structure

It consists of two parts, both essential:

  • Plugin Side (dsh-lan-pair): Starts a LAN listener inside the DSH process, reverse proxies to localhost, automatically completes the DSH token exchange, and holds the session cookie itself.
  • App Side (DSH LAN): Automatically discovers DSH computers on the LAN, enters a 4-digit pairing code once, and connects immediately after opening, displaying the interface in a full-screen system WebView.
  • There is also a deep-link solution for browsers; the phone doesn’t need root, USB debugging, or manual URL construction.

Installing the Plugin

Prerequisites: DSH is installed (this plugin has been verified on 0.1.7-rc.2), and you have a web profile. <profile> is your profile name; when using the official desktop launcher, it is usually the DSH version number (e.g., 0.1.7-rc.2), while using dsh web yourself is usually web.

  1. Enter the profile directory and add dependencies. Windows example cd %USERPROFILE%\.dsh\profiles\0.1.7-rc.2, then:
pnpm add github:FORGE-131/dsh-lan-pair#v0.2.3

To debug by modifying code, use pnpm add link:/path/to/dsh-lan-pair to install from a local directory.

  1. Register the package name in dsh.profile.bundles of $DSH_HOME/profiles/<profile>/package.json:
{
  "dsh": {
    "profile": {
      "bundles": [
        "@deepseek-ai/dsh-base",
        "@deepseek-ai/dsh-web-app",
        "dsh-lan-pair"
      ]
    }
  }
}

This package comes with cordis.patch.yml, which takes effect automatically after registration; no need to manually modify the patch file.

  1. Restart DSH. The startup logs should appear:
[dsh-lan-pair] listening on 0.0.0.0:8099 -> 127.0.0.1:10723
[dsh-lan-pair] pairing is off until you open a window (DSH settings -> Mobile Device)

Broadcasting is off by default: before clicking “Enable Pairing”, the phone cannot scan for this computer. If a Windows Firewall prompt appears, please allow it, or manually allow this single TCP port (subnet only):

netsh advfirewall firewall add rule name="DSH LAN Pair" dir=in action=allow protocol=TCP localport=8099 profile=private,domain remoteip=LocalSubnet

Pairing: Four Steps, 4-Digit Code

  1. On the computer, open DSH “Settings -> Mobile Device” and click “Enable Pairing”.
  2. A window pops up on the computer displaying 4 digits, and broadcasting on the LAN starts simultaneously.
  3. Enter these 4 digits on the phone.
  4. After successful pairing, the window closes automatically, broadcasting stops, and the App obtains long-term credentials, connecting immediately upon opening in the future.

The pairing code is valid for two minutes, can only be used once, and becomes invalid after 5 incorrect attempts. Since the code is only 4 digits, without limits, ten thousand combinations could be tried in a few seconds. If the App malfunctions and manual entry is needed, the long-term key is stored in $DSH_HOME/lan-pair.json (e.g., C:\Users\<you>\.dsh\lan-pair.json), with content like:

{ "secret": "bfrVhwqHdzue50xo", "createdAt": "2026-11-01T21:40:07.737Z" }

Deleting the file and restarting DSH will generate a new one. Environment variables take priority; common ones are:

Variable Purpose
DSH_LAN_PAIR_SECRET Overwrites long-term key
DSH_LAN_PAIR_PORT Overwrites port (default 8099)
DSH_LAN_PAIR_PUBLIC_URL Fixed public address (for naming tunnels / reverse proxy)
DSH_LAN_PAIR_TUNNEL Command to automatically start tunnel; plugin extracts public address from output
DSH_LAN_PAIR_TTL_MS Validity of pairing code (default 120000)
DSH_LAN_PAIR_DEBUG Output mDNS packet diagnostic logs

When the pairing window is not open, you can also manually visit http://127.0.0.1:<port>/lanpair/open to enable it.

When Not on the Same LAN

Pairing is permanent; the long-term key does not expire, and changing networks does not require re-pairing. However, the App remembers “addresses,” and when going out, you need an externally reachable address. Two ways:

Way 1, let the plugin host the tunnel:

$env:DSH_LAN_PAIR_TUNNEL = "cloudflared tunnel --url http://127.0.0.1:8099 --no-autoupdate"

Way 2, fill in a fixed address (naming tunnel / reverse proxy):

$env:DSH_LAN_PAIR_PUBLIC_URL = "https://dsh.example.com"

The plugin provides GET /lanpair/info on the DSH source to check current available addresses, returning something like:

{
  "ok": true,
  "port": 8099,
  "lan": ["http://192.168.1.14:8099"],
  "publicUrl": "https://xxx.trycloudflare.com",
  "pairLink": "https://xxx.trycloudflare.com/__pair?k=...",
  "lanPairLink": "http://192.168.1.14:8099/__pair?k=...",
  "tunnel": { "running": true, "url": "https://xxx.trycloudflare.com" }
}

The plugin writes publicUrl into the mDNS TXT record (remote=). When the App discovers the computer, it gets both the LAN address and the remote address, saves them as an endpoint list, and probes sequentially with a 3-second timeout during connection: it tries the LAN address first (instant connection at home, no public route), and if that fails, tries the remote address (activates automatically when out), using the first successful one without manual switching.

Installing the App

Download dsh-lan-pair-<version>.apk from Releases and sideload it (requires allowing “Unknown sources”). Minimum Android 8.0 (API 26), no CPU architecture restrictions (no native libraries included), renders using system WebView, supports https:// remote addresses and deep links, and does not bundle a browser kernel. The app signature is a self-signed certificate (CN=DSH LAN Pair), not a store signature.

You can create a QR code or shortcut for the connection address; scanning it connects immediately:

dshlanpair://connect?base=<address>&remote=<backup address>&fallback=<further backup>&k=<key>

For example, a link containing both LAN and tunnel addresses:

dshlanpair://connect?base=http%3A%2F%2F192.168.1.14%3A8099&remote=https%3A%2F%2Fxxx.trycloudflare.com&k=<key>

Daily usage workflow:

  1. On the computer, “Settings -> Mobile Device -> Enable Pairing”.
  2. Open DSH LAN on the phone; the computer appears in the list (e.g., DESKTOP-I9IRUS8 (win32) 192.168.1.14:8099).
  3. Enter the 4 digits popping up on the computer and click “Connect”.
  4. Afterward, every time the App is opened it will connect automatically; to switch computers, press the back key to bring up the connection panel.

If it cannot be scanned, confirm that “Enable Pairing” has been clicked on the computer (it won’t broadcast if not clicked), or manually fill in IP:port in the address bar.

Scope and Notes

Suitable for people wanting to control DSH with a phone on the same Wi-Fi, and scenarios where you need to connect back to the home machine after traveling.

A few things you must know:

  • The plugin runs with the current DSH process permissions; you should check the source code and license (MIT) before installing.
  • The plugin depends on the internal DSH service names connection and webServer, as well as connection.authenticatedUrl(). These are not public, stable APIs and may change after DSH updates. Please lock the DSH version you have verified in peerDependencies in package.json.
  • The pairing key is only for preventing accidental connections, not strong encryption: inside the LAN it is plaintext HTTP, and anyone capable of packet sniffing can get the session. It prevents accidental exposure like a neighbor nearby opening it and commanding your computer; for security, please wrap it in a TLS reverse proxy yourself.
  • Cloudflare quick tunnel domains (trycloudflare.com) change every restart, and multiple tunnels built concurrently on the same exit IP will be rate-limited. For long-term use, please configure a named tunnel or use DSH_LAN_PAIR_PUBLIC_URL to point to a fixed domain.
  • App sideloading requires allowing “Install unknown sources”, and the first connection will prompt the Windows Firewall to allow that specific TCP port.

Conclusion

Its value lies in not touching DSH’s security boundaries: it doesn’t modify the listening of dsh web and doesn’t hand the token to the phone. It uses the plugin’s own port and 4-digit pairing code as an intermediate layer, while using mDNS to merge LAN and tunnel addresses into an automatically switchable endpoint list.

  • Directory: https://www.skillhub.cn/plugins/FORGE-131/dsh-lan-pair
  • GitHub: https://github.com/FORGE-131/dsh-lan-pair
羽毛球分组比赛记分
小程序二维码

欢迎使用《羽毛球分组比赛记分》微信小程序

Xiaoye