Replaces the dsh web startup to allow binding 0.0.0.0, gated by username/password login: signed session cookies, /api route protection, an auth tab in the settings panel, and a reset CLI that rotates the signing key to invalidate all sessions.
Install
# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)
dsh plugin --profile web add github:GDWhisper/dsh-web-startup-auth
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
中文 | English
A DSH (DeepSeek Harness) plugin that enables remote web startup with username/password authentication.
⚠️ Version tracking notice: This project only tracks the official
nextdist-tag (the pre-stable release channel) and does not follow thealphapreview channel (current baseline: dsh 0.2.0-rc.1, with all five@deepseek-ai/dsh-*dependencies bumped to^0.2.0-rc.1; seedocs/upgrade-dsh-0.2.0-playbook.mdfor the adaptation review and sentinels).

The stock @deepseek-ai/dsh-web-app/startup hard-rejects --host 0.0.0.0 for safety. This plugin replaces it and adds an auth layer (login/register page + signed session cookies), letting you safely expose dsh web on a LAN or any non-loopback interface.
Features
- Remote startup:
--host 0.0.0.0works, replacing the stock launcher's hard rejection;--host ::(or any IPv6 literal, e.g.::1,fd00::1) works too (#35; non-canonical spellings are canonicalized, zone-suffixed (%eth0) and IPv4-mapped (::ffff:a.b.c.d) ones are refused). On a pure-IPv6 network0.0.0.0binds IPv4 only and the GUI is unreachable;::is dual-stack on Linux (net.ipv6.bindv6only=0) and covers both IPv6 and IPv4-mapped peers; onbindv6only=1systems::is IPv6-only. - Login/register page: A remote first visit guides you through setting the admin credentials, then shows the login page; matches DSH's black/white/blue style.
- Password-free local access: the decision is made per request, not per bind address — a request is trusted only when its TCP peer address and its
Hostheader are both loopback. A browser on the same machine openinghttp://127.0.0.1:<port>/needs no registration or login; LAN clients and requests forwarded by a reverse proxy (Hostnames the public domain) always need a session. - Optional human verification (slider puzzle): a bot check that does nothing much, added at a reader's request — feel free to turn it on and see XD. Flip it on under Settings → Auth → Human verification and a login will ask you to drag a piece into the slot. Each image shows two holes: the piece and the real hole share the same shape (the tab side is drawn fresh per puzzle — right, left, top, bottom, or no tab at all, 5 in all), while the decoy's shape is always different — the piece only fits the hole with the same shape (pure decoration; scripts sail past it anyway). "Does nothing much" is measured, not modesty: the answer has to be drawn in the picture (a human needs to see where to drag), so a few dozen lines of code read it straight out — three conventional variants of this implementation fall to a ~30-line script at 100% (darkened slot), 100% (outline only) and 97% (noise-hardened — and that one a human cannot solve either). The 4-digit image captcha tried earlier fell to template matching at 96.7%. A toy, not a security boundary. See docs/agent/human-verification.md.
- Hardened login rate limiting: 5 failures per IP per 10 minutes locks that client out for 30 s, plus a global exponential backoff — once failures across all sources pass 20, the penalty doubles per step (5 s → 10 s → … capped at 5 minutes), which is what stops distributed stuffing from a thousand addresses; 429 carries
Retry-After. A genuine loopback caller is exempt from the global penalty (unless "require login on loopback" is on), so an attacker cannot use it to lock the administrator out of their own machine. - Session authentication: Signed session cookie (
dsh_sid, default 14-day expiry — configurable in the settings panel (3–180 day choices),HttpOnly+SameSite=Lax). - API protection: Every registered route (
/api/*and third-party RPC routes, except/api/auth/*and/login) requires a valid session, otherwise returns 401 or refuses the handshake. - "Auth" tab in the settings panel: Injects an "Auth" page into the DSH settings panel with Sign out, Change username, Change password, and a session-lifetime selector. The tab shows a shield-with-check glyph in the nav (upstream lets no registrant pick an icon, so the plugin swaps the default gear client-side).
- Remote-scenario fixes (LAN/HTTP pitfalls):
crypto.randomUUIDpolyfill — the API is missing in non-secure contexts; without it every RPC fails.- Native browser-auth bridge — since dsh 0.1.2 (current baseline 0.2.0-rc.1) upstream ships its own browser authentication (signed
dsh-auth-*cookies) and requires the cookie on/apiand onindex.htmlwith no loopback exemption (even the local browser must first swap a launch-token URL). This plugin mints that cookie for callers that already passed ITS authentication — a validdsh_sidsession, or a genuine loopback request (loopback TCP peer and loopbackHost): page navigations pick it up through a single 200 bounce document (a 3xx mint gets replayed on every hop untilERR_TOO_MANY_REDIRECTS, since a cookie set by a redirect response is not sent to that redirect's target) and the login responses hand it out directly, so the printed token URL is never needed. Username/password plus revocable sessions stay the only auth entry point; the upstream cookie merely lets requests through upstream's own gate.
Install
This plugin is a DSH bundle (the dsh.bundle.patch in package.json ships a cordis.patch.yml). After dsh plugin installation the patch layer applies automatically — no manual config editing.
# Option 1: from source
git clone <repo-url>
cd dsh-web-startup-auth
npm install # install build dependencies (typescript etc.)
npm run build # compile src/ to lib/ (the runtime loads lib/ artifacts)
dsh plugin --profile web add .
# Option 2: from the npm registry (re-run the same command to upgrade an existing install)
dsh plugin --profile web add dsh-web-startup-auth@latest
dsh pluginforwards to pnpm and requires--profile <name>;add .installs the current directory as alink:dependency.
Version range: this plugin declares support for dsh
>=0.1.7-rc.1and refuses the0.3.0line (including its pre-releases). When the running dsh falls outside the range, dsh skips this bundle and prints why — deliberately fail-loud: better not to load at all than to run silently on an unverified dsh minor line. The granularity is the minor line: later versions on the same line (including rc pre-releases) load normally; every new minor line needs adaptation before the range is widened. A profile version exemption can force it on if you insist.
Start:
dsh web --host 0.0.0.0
# pure IPv6 networks (or to cover IPv6 too):
dsh web --host :: --port 8080
Or, if applying the patch manually with
--patch ./cordis.patch.yml:dsh --profile web --patch ./cordis.patch.yml --host 0.0.0.0
Usage
- Open
http://<host-ip>:<port>/in a browser (from the same machine usehttp://127.0.0.1:<port>/, which needs no login). IPv6 addresses must be bracketed in URLs:http://[<ipv6-address>]:<port>/. Note: with--host ::the terminal prints only the loopback URL (upstream LAN derivation enumerates IPv4 only), so build LAN IPv6 URLs by hand in the bracketed form. The printed loopback URL is alwayshttp://127.0.0.1:<port>/(upstream hardcodes it); onbindv6only=1systems a::bind is an IPv6-only socket and that URL is dead — usehttp://[::1]:<port>/locally. - A remote first visit redirects to
/login, showing the "set admin credentials" registration form. - After registering you are auto-logged-in and land in the UI; subsequent visits require login.
- Sign out / change username / change password / adjust the session lifetime / turn the slider puzzle on or off: open the Settings panel → Auth tab in the UI (there is also a standalone entry;
POST /api/auth/logoutclears the session cookie).
Credentials and the session secret live in $DSH_HOME/web-auth.json (that is ~/.dsh/web-auth.json when $DSH_HOME is unset, alongside dsh's own data; the DSH_WEB_AUTH_FILE environment variable overrides the whole path):
- Passwords are stored as scrypt hashes (random salt, 64 bytes); plaintext is never saved.
- Session cookies are signed with a random key using HMAC-SHA256 to prevent forgery. The lifetime choice (
sessionMaxAgeDays, default 14) is persisted in this same file and adjustable in the settings panel; changes only affect freshly issued sessions. - Forgot password: on the server machine run
dsh --profile web auth-resetfor an interactive reset (ordsh --profile web auth-reset --password <new-password>non-interactively). Resetting rotates the session secret and invalidates every issued session. - Change username / repair a username containing control characters:
dsh --profile web auth-reset --username <new-username>(can be combined with--password). Also rotates the session secret. Usernames are normalized at register/login/change time by stripping C0 control characters (0x00–0x1F) and DEL (0x7F) — if an older version already stored a DEL-polluted username verbatim, this command repairs it. - Fallback: delete the credential file (
$DSH_HOME/web-auth.json, default~/.dsh/web-auth.json) and restart to re-register (also invalidates all sessions, but requires a restart).
Index
If you are looking for an out-of-the-box IDE built for the Agent era, check out Omniterm
Security notes
- This plugin provides authentication but not transport encryption. Over plaintext HTTP, credentials and traffic can be sniffed on the same network — use only on a trusted LAN or put an HTTPS reverse proxy in front.
- Sessions last 14 days by default, adjustable in the settings panel's "Auth" tab (3/7/14/30/60/90/180 day choices, persisted in the credential file); changes only affect freshly issued sessions — existing ones keep the lifetime they were signed with.
- Password hashing uses Node's built-in
crypto.scryptSync; no third-party dependency. - Sessions cannot be revoked server-side:
dsh_sidis a self-contained signed cookie;/api/auth/logoutonly clears it on the browser side. A leaked cookie (e.g. sniffed over plaintext HTTP) cannot be individually revoked within its lifetime (bounded by the session-lifetime choice made in the settings panel). Exceptions:dsh --profile web auth-reset, the "Change password" and "Change username" actions in the settings panel all rotate the session secret, invalidating all sessions at once (after the change the current session is re-issued, so you stay signed in). - First-registration window: while no credentials are set, any visitor can register as admin. Complete the first registration before exposing the service to an untrusted network.
- Login throttling: login failures are rate-limited per client IP — 5 consecutive failures lock the client out for 30 seconds (in-memory only, not persisted); registration requires a password of at least 8 characters. Throttling covers
/api/auth/login,/api/auth/change-password, and/api/auth/change-username(a wrong old/current password also counts). For stricter protection, add general rate limiting at your reverse proxy. - Two-layer API session gate: every protected route is denied by default at the registration layer (unauthenticated navigations get a 302 to the login page, everything else 401); the shared API additionally carries a session gate mounted on upstream's official
connection/requestextension point. The two layers insure each other: if an upstream interface change disables one, the other still guarantees that only a revocable session (or a genuine loopback caller) can reach the API — the upstream native cookie, which cannot be revoked on its own for 30 days, never works as an API credential by itself. - Credential file permissions:
$DSH_HOME/web-auth.json(default~/.dsh/web-auth.json; password hash + session signing key) is saved with0600, its directory with0700; the plugin repairs overly-broad permissions left by older versions at startup. --trusted-host: kept only for CLI compatibility with the stock launcher; it plays no part in this plugin's auth decisions — remote clients always need a valid session; there is no "trusted host skips login".- Reverse-proxy deployments (nginx, …): binding dsh to
127.0.0.1and letting the proxy terminate TLS and forward is supported. The proxy must forward the realHost(nginx does by default viaproxy_set_header Host $host;; pass--trusted-host <domain>so DSH's own Host fence accepts it); once authenticated, the plugin mints the upstream native browser cookie under the request's realHost(the public domain), so upstream's gate accepts the request. If the proxy instead hard-codesHost: 127.0.0.1, the plugin reads the request as local and lets all traffic through unauthenticated — do not configure it that way.X-Forwarded-Foris never consulted (a client can forge it); trust is decided solely by the TCP peer address andHost. - Upstream compatibility (dsh 0.1.7 baseline): From dsh rc.8 through 0.1.1, DSH's frontend decided loopback-ness from the browser address bar hostname (
connection.isLoopback), so in a remote browser the settings mirror ran in memory mode and plugin-config cards / the Models page were unusable; this plugin injected a script into the SPA index flipping that flag to a constanttruethe moment the connection plugin activated. 0.1.2's real cookie authentication gets a remote browser into the UI, but the settings mirror still keys off the same flag — a LAN browser still gets amemorymirror that never reads the host, and the Models (provider directory) section of the settings panel reports "settings are unavailable in this browser". Restoring the old getter override breaks the web boot (A/B verified: 26 frontend plugins stayed pending), so as of 0.1.2 the fix is instead injectingwindow.__DSH_TRANSPORT__ = { ownsHost: true }: the connection client reads that hook at construction and reportsisLoopbackastrue(api/rpc fields fall back safely when absent, and the cordis service is not rewritten), so every settings surface — Models included — renders normally from both LAN and loopback browsers. Remaining browser-side shims: that transport hook and thecrypto.randomUUIDpolyfill (needed for plaintext-HTTP non-secure contexts).
Development
npm install
npm run typecheck # tsc --noEmit
npm test # vitest
npm run build # tsc -p tsconfig.json + tsdown, output to lib/
tsccompiles the node-side source (src/*.ts) and type declarations tolib/andlib/types/.tsdownbundles the frontend plugin (src/client/index.tsx) into the browser bundlelib/client.js(thewindow.__ModuleLoader__.loadregistration format). Rebuild after changing frontend code; alink:install in the profile picks up new artifacts automatically.- The
@deepseek-ai/dsh-client-*packages the frontend plugin depends on are used only for types and building; at runtime they are provided by DSH's frontend module table.
License
Links
More in this category
toby-bridges/api-relay-audit★ 865
Runs local security audits of AI API relays and LLM proxies from DeepSeek Harness, producing Markdown reports for prompt injection, model substitution signals, tool-call rewriting, error leakage, stream integrity, and profile-gated Web3 risks.
SeaOf0/dsh-redteam-model★ 659
Authorized-security DSH collection: nine work modes (redteam coordinator, pentest, code audit, binary analysis, attack-defense, AV evasion, incident response, cloud security, CTF solving) and fifteen runtime plugins, managed from a settings page with one-click deploy, install, update and uninstall.
howmp/dsh-pentest★ 581
Authorized pentest mode for DeepSeek Harness — exploration chain, assets and findings with a Web view.
PerryLink/dsh-auto-review★ 224
Second-model auto-review on the approval answerer chain: a read-only reviewer subagent returns structured allow/deny verdicts with reasons, fail-closed by default.
NanmiCoder/dsh-auto-mode★ 164
Adds an Auto permission preset between Workspace Write and Full access: routine work stays in the official workspace-write sandbox while the current session model reviews escalation and destructive calls, granting one exact wider access once, asking when the intent is ambiguous, and denying critical paths.
PerryLink/dsh-permission-rules★ 116
Claude Code-style declarative permission rules: ordered allow/deny/ask YAML rules matching tool names, arguments, workspace paths, and agent identity on the tools/pre-execute waterfall, with full session-log audit, dry-run mode, and hot reload.
Community comments
Comments are public GitHub Discussions. Loading them connects to GitHub and Giscus; a GitHub account is required to post.