dsh-web-auth
August 14, 2026 · View on GitHub
Password gate for the DeepSeek Harness Web shell — a login page in front of the GUI, as a standalone installable package.
The official @deepseek-ai/dsh-host-webserver has no request-interception extension, so a password gate cannot be built as a pure plugin against it. This package ships a drop-in webserver fork that adds one extension point — webServer.registerGate(gate), a request-gate seat running before route matching on every HTTP request and WebSocket upgrade — plus the auth plugin (dsh-web-auth/web-auth) that implements the gate: a /login page, an HMAC-signed session cookie, and 401 for /api and upgrade requests.
Everything else (SPA dist serving, /api bridge, WebSocket downlinks, trust fence) is untouched and comes from the official packages as usual. Verified against the official @deepseek-ai/dsh (0.1.0-rc.6): SPA, /api, and WebSocket all work behind the gate.
Prerequisites
- Node.js
^22.19 || >=24(the official dsh requirement) - The official CLI works at least once:
npx @deepseek-ai/dsh web(stop it withCtrl+Cafter it prints the URL line) — this creates your harness home and thewebprofile - Your harness home defaults to
~/.dsh(override with theDSH_HOMEenvironment variable); the profile lives at~/.dsh/profiles/web
You do not need pnpm installed — the install command below runs pnpm 11 via npx, matching the pnpm major the official dsh declares. Using a locally installed pnpm of a different major against the same profile produces ERR_PNPM_UNEXPECTED_STORE (see Troubleshooting).
Quick start
1. Install the package
cd ~/.dsh/profiles/web && npx -y pnpm@11 add -w dsh-web-auth
Why this exact command:
dsh-web-authis a plugin library, not a CLI — it has nobinand cannot be "run" withnpx. It is loaded by the cordis loader fromnode_modulesby name, so it must be installed into the profile with a package manager, which is whatadddoes.npx -y pnpm@11runs pnpm 11 without requiring pnpm on your machine. The official dsh declarespackageManager: pnpm@11.7.0; a locally installed pnpm of a different major operating on the samenode_modulesfails withERR_PNPM_UNEXPECTED_STORE(see Troubleshooting). If you already have pnpm 11 (npm install -g pnpm@11), the plainpnpm add -w dsh-web-authis equivalent.- The
cdis required: newer pnpm (11.21+) rejects--workspace-rootunless the current directory is inside a workspace, and--dirdoes not satisfy that check. -witself is required: the profile is a pnpm workspace root, and adding to it without-wfails withERR_PNPM_ADDING_TO_ROOT.dsh plugin --profile web add ...hits the same wall.
2. Configure
Replace the contents of ~/.dsh/profiles/web/cordis.patch.yml with:
- id: webserver
disabled: true
- insert:
- id: webserver-gated
name: 'dsh-web-auth'
inject: [webStartup]
config:
host: !!js ctx.webStartup.host ?? '127.0.0.1'
port: !!js ctx.webStartup.port ?? 3080
- id: web-auth
name: 'dsh-web-auth/web-auth'
config:
password: !!js process.env.DSH_WEB_PASSWORD
What this does: disables the official webserver row (it has no gate), inserts the gated fork in its place, and mounts the auth plugin. The password is read from the DSH_WEB_PASSWORD environment variable at startup — never commit it to a config file.
3. Start
DSH_WEB_PASSWORD='your-password' npx @deepseek-ai/dsh web
4. Verify
- Open
http://127.0.0.1:3080— it must redirect to a login page ("此界面受密码保护"). - Enter the wrong password — it must show "密码错误,请重试。".
- Enter the right password — you land on the GUI.
- Without a session cookie,
/apirequests and WebSocket connections are refused with 401.
Optional: set DSH_WEB_SESSION_SECRET to a random string so sessions survive restarts (without it, everyone logs in again after a restart).
Remote access
Three things gate remote use of the GUI: the password gate (yours), HTTPS (required by the browser), and the official /api trust fence (Host-header allowlist). All three are covered below.
-
LAN / Tailscale: change
hostto'0.0.0.0'in thewebserver-gatedrow above and restart. The/apitrust fence automatically trusts the machine's own LAN/Tailscale IPs when the server binds0.0.0.0. Tailscale traffic is encrypted by WireGuard, so the password stays safe on that path. -
Internet — HTTPS is required, not optional: the browser only exposes
crypto.randomUUID()in a secure context (TLS), and the official GUI calls it (e.g. creating a workspace); a plainhttp://<lan-ip>:3080page crashes withcrypto.randomUUID is not a function. Front the server with TLS — Cloudflare Tunnel or a reverse proxy — and keep the origin bound to127.0.0.1. The login page works behind TLS without changes. -
Custom hostname / public domain — the
/apitrust fence: every/apirequest whoseHostis neither loopback nor in the trust list is answered 403, so browsing a directory or loading sessions throughhttps://your.domainfails until you declare the domain. Add it to theconnectionrow in~/.dsh/profiles/web/cordis.patch.yml:- id: connection inject: [webRuntime] config: trustedHosts: !!js ctx.webRuntime.trustedHosts.concat(['your.domain'])(
--trusted-host your.domainon thedsh webcommand works too, but must be repeated every start.) Note the!!jsexpression must be a single scalar — array-literal syntax like!!js [...x, 'y']fails YAML parsing.
Known official limitations over non-loopback access
- Privileged methods stay loopback-pinned. The official client pins
host.pickDirectory,settings.*,credentials.*and friends to loopback even on trusted-host deployments ("until a real authentication layer exists"). Directory browsing (host.listDirectory) works once the domain is trusted, but the final "open/confirm" step of choosing a workspace directory may still answer 403 — do that step on127.0.0.1, then continue remotely. crypto.randomUUID(secure context) — see "Internet" above; this is an official bug (no feature detection), not something this package can patch.
Troubleshooting
ERR_PNPM_UNEXPECTED_STOREwhen runningpnpm add— yournode_moduleswas linked by a different pnpm major. Use pnpm 11 (npm install -g pnpm@11), which is the official dsh requirement.--workspace-root may only be used inside a workspace— newer pnpm requires the current directory to be inside a workspace for-w;cd ~/.dsh/profiles/webfirst, then run theaddfrom there (the README command already does this).- Login page does not appear — the gate is not armed. Check that
dsh-web-authis in~/.dsh/profiles/web/package.jsonand that the patch file has no YAML errors. /apianswers 403 through a custom host — the trust fence needs the authority; see "Custom hostname" above (theconnectionrow patch, not--trusted-hostwhich resets every start).- Creating a workspace fails with
crypto.randomUUID is not a function— the page is not a secure context; access through HTTPS (Cloudflare Tunnel / reverse proxy), never plainhttp://<non-loopback-ip>. - Directory browsing works but "Open" still 403s remotely —
host.pickDirectoryis a loopback-pinned privileged method in the official client; choose the directory on127.0.0.1once, then continue remotely. - Port 3080 already in use — another
dsh webis running; stop it (pkill -f "dsh web") or pass--port <other>. - Verify your composition without starting the server:
npx @deepseek-ai/dsh web --dump-configshows the merged rows — you should seewebservermarkeddisabled: true, pluswebserver-gatedandweb-authinserted. - Official dsh updated — the webserver fork is a small delta over
@deepseek-ai/dsh-host-webserver(registerGate+ upgrade gating). If the official package changes, syncsrc/webserver.tsfrom upstream and re-runnpm run build(seenpm run buildbelow).
Building from source
npm install
npm run build # tsc -> lib/
How it works
dsh-web-auth(default export):WebServer, the official webserver plusregisterGate(gate)— a single-owner seat consulted before route matching on every request and before upgrade dispatch on every upgrade. Denials own writing the response (the realServerResponseon HTTP, a raw-socket adapter on upgrades).dsh-web-auth/web-auth: the auth plugin. Idle when no password is configured; when armed it whitelists/login, admits valid-cookie requests, answers/apiand upgrades with 401, and redirects other GET/HEAD navigations to/login. Password and signature comparisons are constant-time.
License
MIT. The webserver fork derives from @deepseek-ai/dsh-host-webserver (MIT, DeepSeek Harness); the auth plugin is original.