Minimal, upgrade-safe remote access to the DeepSeek Harness (dsh) Web UI from your phone over Tailscale Serve — dsh never leaves loopback.
dsh web binds 127.0.0.1:3080 by design and refuses --host 0.0.0.0.
To use the UI from a phone over Tailscale, keep dsh on loopback and let
tailscale serve terminate HTTPS from your private tailnet and proxy to that
port. Only official flags are used (--trusted-host, --no-open, --port);
no dsh source or node_modules is touched, so rc upgrades are far less likely
to break.
| Fact | Value |
|---|---|
| What it serves | DeepSeek Harness (dsh) Web UI |
| dsh bind | 127.0.0.1:3080 (loopback only, by design) |
--host 0.0.0.0 |
rejected by dsh (do not use) |
| Remote access | Tailscale Serve (HTTPS on your tailnet) |
| Public internet | never (never tailscale funnel) |
| Required dsh flag | --trusted-host <node>.tailXXXX.ts.net |
| Launcher | bash start-dsh-web.sh |
| Hostname config | local.conf (gitignored) or $DSH_TS_HOST |
| First-use auth | visit ?token=... URL once → 30-day signed cookie |
| Verified against | dsh 0.1.2-rc.1, Tailscale 1.102.3, Ubuntu 26.04 |
start-dsh-web.sh one-shot launcher (stop → start with trust flag → print URLs)
README.md this file
README.zh.md 简体中文
docs/TROUBLESHOOTING.md auth/token internals, 401 vs 403, persistence, alternatives, boot autostart
local.conf generated at --configure; gitignored (holds hostname)
LICENSE, SECURITY.md
DeepSeek Harness is a plugin-first agent harness whose Web UI can read files,
run shell commands, and write disk — a remote-code-execution surface. For that
reason dsh binds to loopback and refuses --host 0.0.0.0. You therefore cannot
use a phone browser straight at http://<ip>:3080; you need a tunnel.
Tailscale Serve is the least-moving-parts, tailnet-only option: it
terminates HTTPS, exposes only to your own devices, and proxies back to the
loopback port. The UI stays on loopback (smallest dsh attack surface) and dsh
internals stay untouched (rc upgrades don't break you). Cleaner/faster
alternatives exist (Cloudflare Tunnel + Access, zero-trust gateway plugin) but
are heavier; binding 0.0.0.0 + a password wall is explicitly less safe.
phone/desktop browser (tailnet)
│ https://<node>.tailXXXX.ts.net/ (TLS by Serve)
▼
Tailscale Serve (terminates HTTPS; no header injection here)
│ plain HTTP proxy
▼
dsh web 127.0.0.1:3080 (the ONLY listener that can touch dsh)
dshonPATH, with a Web profile (~/.dshpresent)- Tailscale: logged in, node on your tailnet, MagicDNS enabled
- A
serveroutehttps://<node>.tailXXXX.ts.net/→127.0.0.1:3080 - Phone / other device on the same tailnet
# 0. Configure tailnet hostname once (find it via `tailscale status` → self node)
bash start-dsh-web.sh --configure '<node>.tailXXXX.ts.net'
# 1. Serve maps 3080 (needs sudo once, 443)
sudo tailscale serve --bg 3080
tailscale serve status # verify
# 2. Launch dsh web for remote use
bash start-dsh-web.sh
# prints:
# Local access : http://127.0.0.1:3080/?token=...
# Phone FIRST visit (exchanges token for a 30-day cookie):
# https://<node>.tailXXXX.ts.net/?token=...Phone (browser, tailnet): open the ?token=... URL once → dsh mints a signed,
30-day cookie (persisted across dsh restarts). After that, https://<node>.…/
just works.
The browser-auth applies to any origin, including 127.0.0.1:3080 on the
machine itself. If you open 127.0.0.1:3080 and get
dsh web authentication required; reopen the URL printed by dsh web, that's
normal — you must first visit once with the token:
# one-off token exchange (localhost)
http://127.0.0.1:3080/?token=<CURRENT_TOKEN>Then plain http://127.0.0.1:3080/ works for 30 days on that browser.
When dsh web runs under the systemd user service (dsh-web.service) the URL is
not printed to your terminal — it goes to the journal. Read it from there:
journalctl --user -u dsh-web.service --no-pager | grep -oE 'token=[A-Za-z0-9_-]*' | tail -1(The start-dsh-web.sh launcher prints it directly instead.) Every dsh web
start mints a new token, so re-read it after any restart. See
docs/TROUBLESHOOTING.md for the full auth model.
Both ways to run dsh web — the launcher script and the systemd autostart service — and how to control each. Pick the one you use.
If you set up the boot service (dsh-web.service), this is the everyday path:
systemctl --user status dsh-web.service # is it running? (→ active)
systemctl --user restart dsh-web.service # restart (mints a NEW token)
systemctl --user stop dsh-web.service # stop (phone fails until start)
systemctl --user start dsh-web.service # start again
systemctl --user disable --now dsh-web.service # turn OFF boot autostart
journalctl --user -u dsh-web.service --no-pager | grep -oE 'token=[A-Za-z0-9_-]*' | tail -1 # current tokenRestart=on-failure relaunches it automatically on a crash; use the manual
commands only when you want control or a fresh token.
bash start-dsh-web.sh # start (prints local + phone URLs with token)
pkill -f "dsh web --trusted-host <node>.tailXXXX.ts.net" # stopThe token changes. Re-read it (journalctl ... above, or the script's output)
and have the phone/browser do the ?token= exchange again only if its
30-day cookie has expired or it's a new browser.
tailscale serve --bg --https 8443 http://127.0.0.1:3080 # URL uses :8443- Serve exposes only to your own tailnet — not the public internet.
- Never
tailscale funnel— that exposes a shell-capable UI to the world. - After first visit, the 30-day cookie is the access boundary; revoke = delete cookie.
- dsh's
/apifence is anti-DNS-rebinding / cross-origin, not auth. Serve's private tailnet is your network-level auth. For per-identity auth seedocs/TROUBLESHOOTING.md → Alternatives(dsh-one-gateway,dsh-auth-tailscale, read Serve'sTailscale-User-Loginheader). - Before pushing a change, scan for real identifiers per
SECURITY.md.
- Linux (verified Ubuntu 26.04 Desktop); pattern is cross-platform (macOS/WSL2).
- Node.js (dsh usually supplies its runtime).
MIT — see LICENSE.
DeepSeek Harness is a developer preview. Everything here is verified against a
specific dsh rc version and dsh APIs change fast. After an upgrade, re-check the
checklist in docs/TROUBLESHOOTING.md.
Open an issue or PR. Keep change-scans per SECURITY.md.