JUANWANG-BUAA/dsh-full-remote

JUANWANG-BUAA/dsh-full-remote

远程访问 DeepSeek Harness 且服务端 API 完整:转发时改写 Host/Origin,恢复其他方案必定 403 的 settings.* / credentials.* / host.listDirectory。令牌门、按设备会话、可选首访审批。

远程与移动端aiauthenticationcloudflared

dsh-full-remote

Listed in awesome-dsh-plugin · DeepSeek Harness plugin

Current release: v0.5.0 · Distributed through GitHub Releases

English | 中文

dsh-full-remote is a plugin for DeepSeek Harness. It places an authenticated reverse proxy in front of the Harness Web server, so the Web UI can be used through a public tunnel or from a device on the local network while privileged APIs such as settings, credentials, and directory browsing remain available.

New in 0.5.0

  • A selectable cross-session Task cockpit for approvals, questions, plan review

and session navigation; page-lifetime drafts, offline guards and no write replay.

  • Negotiated Brotli/gzip, device-isolated encoded static caching, an owned upstream

connection pool, activity-aware expiry and visibility-aware, non-overlapping polling.

  • Keeps DeepSeek Harness 0.2.1-rc.2 compatibility and the existing authentication

and loopback-only proxy-management boundary.

Measured gains — and trade-offs

Fifteen paired local HTTP rounds against the previous already-gzipped version; these are cumulative request times, not LCP or real mobile page-load measurements.

Scenario · Baseline → 0.5.0 candidate · Meaning Cold HTML + 4 entry assets · 42.08 → 25.61 ms; 415,637 → 415,534 B · −39.1% HTTP time, only 0.025% fewer body bytes 40 warm static requests · 424.68 → 28.15 ms; upstream requests 40 → 0 · −93.4% HTTP time, not 100% less downstream traffic 40 conditional requests · Body 0 → 0 B; headers 6,360 → 9,800 B · Both already return 304; candidate avoids upstream but adds metadata 10 synthetic dynamic JSON requests · 8.82 → 10.25 ms · 16.2% slower locally; no API caching Real React components, simulated 60 s · Hidden RPCs 50 → 0; slow visible RPCs 48 → 20 · Normal visible RPCs stay 50; not a battery-life claim

Full method, same-encoding controls, limits and reproduction: English benchmark · 中文完整报告.

60-second quick start

curl -fLO https://github.com/JUANWANG-BUAA/dsh-full-remote/releases/download/v0.5.0/dsh-full-remote-0.5.0.tgz
dsh plugin --profile web add ./dsh-full-remote-0.5.0.tgz
dsh --profile web

In Settings → Reverse proxy, press Start proxy, then Start Cloudflare quick tunnel and scan the generated QR code. The invite is one-time and never contains the standing access token. For a controlled network, point an existing SSH, frp, ngrok, Tailscale, or cloudflared tunnel at the proxy target shown in the panel instead.

The quick tunnel is optional and temporary, not a managed production deployment. Read Security model before exposing a listener to the Internet. For composition details, see Compatibility and composition.

Desktop control panel · Mobile workspace ·

Task cockpit inbox · Cockpit question form ·

Problem

DeepSeek Harness binds its Web server to a loopback address and only accepts privileged requests when the Host and Origin headers refer to a loopback address. When the UI is reached through a generic tunnel, these headers carry the public hostname and the trust check fails. The page loads, but the following methods return 403:

  • settings.*
  • credentials.*
  • host.listDirectory

Approach · Result Generic tunnel (SSH port forward, Caddy, binding 0.0.0.0) · Page loads; settings.* / credentials.* / host.listDirectory return 403 LAN-only plugin without authentication · Usable on the local network; not suitable for public exposure Password prompt without header rewriting · Requests are authenticated, but the privileged APIs remain blocked

Solution

The plugin inserts a reverse proxy between the tunnel and the Harness Web server. The proxy:

  • rewrites Host and Origin to 127.0.0.1 before forwarding, so the

privileged APIs pass Harness's trust check;

  • requires an access token or a valid device session before any request is

forwarded;

  • forwards HTTP, SSE, and WebSocket traffic; compressible HTTP responses

negotiate Brotli/gzip when eligible (not SSE or WebSocket);

  • provides a settings page (Settings → Reverse proxy) for starting and

stopping the proxy, changing the listen address, rotating the token, and managing device sessions.

Because the rewrite disables Harness's original trust check for remote clients, the plugin provides its own access-control layer in its place. This layer is described under Security model.

The plugin can optionally start a temporary Cloudflare quick tunnel. Any managed tunnel (cloudflared, ngrok, frp, SSH, Tailscale) can also point at the local endpoint it publishes.

How it works

flowchart LR
    A[Phone or remote browser] --> B[Public tunnel<br>cloudflared / ngrok / frp / SSH]
    B --> C[dsh-full-remote<br>127.0.0.1:3081<br>authentication + header rewrite]
    C --> D[DeepSeek Harness Web<br>127.0.0.1:3080]
  1. The remote browser connects to the public tunnel, which forwards to the

plugin's listener (127.0.0.1:3081 by default).

  1. A request is accepted only with an access token, a valid one-time

invite, or an existing device session. Requests that fail authentication do not reach the backend.

  1. The proxy rewrites Host/Origin to loopback, removes untrusted

headers, and forwards the request to the Harness Web server at 127.0.0.1:3080. Compressible HTTP responses (HTML/JS/CSS/JSON/SVG, ≥1 KB) may use Brotli/gzip; SSE and WebSocket are not compressed. Eligible hashed /assets/* files receive private immutable caching and bounded device-isolated server variants. HTML/API are not stored in that static cache. See HTTP transport and the benchmark.

Features

Privileged APIs

  • settings.describe / update / replace / mutate
  • credentials.describe / set / unset
  • host.listDirectory / pickDirectory / openPath
  • agentPreset.*, llm.discoverModels

Access control

  • 192-bit access token, stored in a state file with mode 0600; reveal and

rotation are performed from the local panel

  • Per-device sessions: each login creates an independent device

credential, and only a hash is persisted. Devices can be renamed or revoked from the panel, which also shows each device's source IP (at login and most recently seen).

  • Optional first-visit approval: a new device waits on a page until it is

approved from the local panel

  • Phone invite: a QR code or a one-time link (single use, 15-minute

expiry). Same-IP browser retries within 60 s reuse the original device session so a flaky tunnel dropping the redirect cannot deadlock the phone into the token form or spawn a duplicate device. The link does not contain the standing token.

  • Fixed delay and per-IP lockout on failed logins
  • Optional CIDR allowlist for remote IPs
  • Optional trustForwardedFor to use real client IPs from a trusted local

tunnel in CIDR / rate-limit / audit via its rightmost X-Forwarded-For value; CF-Connecting-IP is a separate Cloudflare-only opt-in, and loopback or malformed forwarded values are never trusted

Operation

  • Fence self-check: probes settings.describe with the same Host/Origin

rewrite the proxy uses

  • Structured JSONL audit log (login, approval, revocation, token rotation,

start, stop, WebSocket open/deny/reject) with an in-panel viewer for recent events and JSON export; rotates past 8 MB, keeping one previous generation

  • Runtime listen-address changes with automatic rollback when a bind fails
  • Optional local TLS (tlsCertFile / tlsKeyFile)
  • Health endpoint at /_dsh_reverse_proxy/healthz
  • WebSocket upgrade rate limiting: repeated failed upgrades are locked out

per remote IP

  • Stream-level request body limit; hop-by-hop and spoofable headers are

stripped; upstream set-cookie is removed

  • Negotiate Brotli quality 4 / gzip by client q values; respect no-transform and skip SSE, WebSocket, ranges, pre-encoded and tiny bodies.
  • Admitted hashed /assets/* use private immutable browser caching and a device-scoped encoded LRU (8 MiB / 128 entries / at most five minutes). ETag hits can return 304; authentication/CIDR precede hits. The server-side static cache excludes API, HTML, Set-Cookie and unknown Vary responses; browser caching still respects upstream policy.
  • An owned upstream keep-alive pool reduces repeat connections. Actual bytes maintain device activity without extending absolute TTL; revoked or expired quiet streams close both peers. Background polling pauses and recovery refreshes coalesce.
  • Disable via compressResponses: false / cacheHashedAssets: false. See HTTP transport for the contract and the benchmark for measured gains and regressions.

One-click public tunnel (Cloudflare quick tunnel)

  • Start a cloudflared quick tunnel from the panel (free, no account) and

get a https://…trycloudflare.com address — no public IP or port forwarding required

  • Binary resolution: cloudflaredPath → PATH → a pinned

(2026.8.2), SHA256-verified download cache; failed checksums are discarded

  • While the tunnel is up, forwarding-header trust applies dynamically

(rate limiting / CIDR / audit see real client IPs) and reverts when the tunnel stops; the tunnel forwards to the proxy listener, so the token gate, approval and audit all keep applying

  • Invites automatically use the tunnel URL: start the tunnel, generate

the QR, scan from the phone (the panel shows it and the Origin can still override it)

  • Mutually exclusive with local TLS (the Cloudflare edge already