@pananfly/dsh-auth-access
Repository: https://github.com/pananfly/dsh-auth-access · npm: @pananfly/dsh-auth-access
中文 | English
Auth + LAN proxy for dsh web — password/TOTP gate via a :: dual-stack proxy, internal 127.0.0.1:3080 webServer, optional isLoopback patch for remote settingsScope.
Educational / trusted-network only. The proxy exposes the Harness UI over plain HTTP. Anyone who can reach the enabled address gains full UI access.
What it does
127.0.0.1:3080 internal — the original dsh webServer, no auth, loopback-only
[::]:3082 public proxy (dual-stack, covers 0.0.0.0) — the only public entry, gated by HttpOnly HMAC cookie + optional TOTP, forwards with Host/Origin → 127.0.0.1:3080 and keepAlive+ gzip
- Strict env gate —
DSH_WEB_AUTH_PASSWORD is the master switch; PORT/TOTP/PATCH are ignored without it (plugin inactive)
- Optional
isLoopback patch — DSH_WEB_AUTH_PATCH=1 MITM-rewrites /plugins/@deepseek-ai/dsh-client-connection/client.js to isLoopback:true so remote settingsScope uses host instead of memory (proxy-only, localhost:3080 unaffected)
- Hybrid directory picker (C1, gated by
PASSWORD + PATCH=1) — only when both DSH_WEB_AUTH_PASSWORD and DSH_WEB_AUTH_PATCH=1 are set, the plugin disables upstream directory-picker (dsh-host-directory-picker-auto) and mounts directory-picker-hybrid: local browsers get the native OS chooser (osascript/zenity/Win32 COM), remote browsers get the in-app Select Workspace Directory Miller browser (browse backend). Unsets keep upstream auto (127.0.0.1+darwin→native/linux→DISPLAY+chooser check) untouched. Plugin list gains one row directory-picker-hybrid only while gated active; otherwise no extra row.
crypto.randomUUID polyfill — triple-fallback injected via tapIndex for plain-http insecure contexts
Architecture
browser --http://[::]:3082/--> auth-proxy (gate: login/TOTP, MITM patch?)
--Host/Origin->127.0.0.1:3080--> internal webServer (polyfill, no gate)
--> DSH host /api (loopback fence sees 127.0.0.1:3080 → pass)
--> Harness
web-access-startup | @pananfly/dsh-auth-access/startup | webStartup{host:127.0.0.1, proxyPort, auth, totp, patch} | replaces web-startup, forces internal host |
web-access-webserver | @pananfly/dsh-auth-access/webserver | webServer (API-compatible, polyfill only) | replaces webserver, 127.0.0.1:3080 |
web-access-proxy | @pananfly/dsh-auth-access/proxy | authProxy (gate+forward+patch) | new, ::3082 |
dsh-auth-access | @pananfly/dsh-auth-access | anchor | client scan (platform:web) |
directory-picker-hybrid | @pananfly/dsh-auth-access/directory-picker-hybrid | directoryPicker (hybrid: pick→native, list/create→browse) | gated PASSWORD+PATCH=1 only; disables directory-picker when active, otherwise absent from loader/plugin-inventory |
Key implementation points
- No per-IP switches — single
:: listener (covers IPv4+IPv6), no subprocess per address, no shell probing
- Single internal gate move — auth moved from
webServer to proxy; webServer keeps only tapIndex polyfill + 127.0.0.1 bind; proxy does doubleDecodePathname, 401/302, loginLimiter 5/5m, timingSafeEqual, HttpOnly;SameSite=Lax 7-day stateless HMAC cookie, sessionSecret per-process, WS upgrade 401
- MITM patch —
forwardHttp buffers only PATCH_PATH=/plugins/@deepseek-ai/dsh-client-connection/client.js when DSH_WEB_AUTH_PATCH=1, isPatchTarget && status200 && !content-encoding, replaces isLoopback: pageLocation === void 0 || isLoopbackHostname(pageLocation.hostname), → isLoopback: true,, re-emits with corrected content-length or gzip; on miss logs [dsh-auth-access proxy] patch failed ...
- Hybrid picker gate —
cordis.patch.yml uses disabled: !!js guards: directory-picker disabled iff !!PASSWORD && PATCH==="1", directory-picker-hybrid disabled iff !(!!PASSWORD && PATCH==="1"). When gated off the hybrid package is not loaded, so loader.store/plugin-inventory shows no extra row and upstream dsh-host-directory-picker-auto (resolve.ts: bindHost/SSH/platform/DISPLAY+linuxChooser) remains authoritative. When gated on, invasiveness is 1 host + 1 client package (~300 lines), per-request remoteAddress routing (127.0.0.1→native, else→browse), linuxChooser/DISPLAY check kept after remote check, maxEntries=1001 bounded window unchanged — no measurable perf delta.
- Id naming — all three replacement rows contain
web-access- prefix (web-access-startup/webserver/proxy) for clarity
- Scoped package —
name:@pananfly/dsh-auth-access, publishConfig.access:public, exports: .,/startup,/webserver,/proxy,/client, dsh.bundle.patch:./cordis.patch.yml, dsh.client.platform:web
Usage
Install
dsh plugin --profile web add @pananfly/dsh-auth-access
dsh plugin --profile web add ./path/to/dsh-auth-access
Environment variables (strict)
DSH_WEB_AUTH_PASSWORD | yes (master) | — | Enables the plugin; without it AUTH_PORT/TOTP/PATCH are ignored and plugin is inactive ([dsh-auth-access] plugin inactive) |
DSH_WEB_AUTH_PORT | no | 3082 (auto when password set) | Proxy listen port on :: dual-stack |
DSH_WEB_AUTH_TOTP_SECRET | no | — | Base32 TOTP secret (requires password); ignored without password |
DSH_WEB_AUTH_PATCH | no | 0 | 1 to enable isLoopback MITM patch (remote settingsScope → host) and hybrid directory picker (local native / remote browse). Hybrid requires PASSWORD + PATCH=1 together (see below); either unset keeps upstream picker. |
Strictness: any of PORT/TOTP/PATCH set without PASSWORD logs ignored without DSH_WEB_AUTH_PASSWORD (plugin inactive) and is not applied. Hybrid picker additionally requires PATCH=1; PASSWORD alone keeps upstream directory-picker-auto (mac darwin→native still shows host osascript dialog for remote — use PATCH=1 to get remote Miller browser).
Examples
dsh web
DSH_WEB_AUTH_PASSWORD=secret dsh web
DSH_WEB_AUTH_PASSWORD=secret DSH_WEB_AUTH_TOTP_SECRET=JBSW... dsh web
DSH_WEB_AUTH_PORT=4000 DSH_WEB_AUTH_PASSWORD=secret dsh web
DSH_WEB_AUTH_PATCH=1 DSH_WEB_AUTH_PASSWORD=secret dsh web
DSH_WEB_AUTH_PASSWORD=secret dsh web
dsh web --generate-totp
Open http://<host>:3082/ on your phone/LAN, login at http://<host>:3082/__auth/login.
Console
[dsh-auth-access proxy] listening on [::]:3082 -> 127.0.0.1:3080 auth=password patch=disabled (DSH_WEB_AUTH_PATCH=0)
[dsh-auth-access] webServer internal on 127.0.0.1:3080 (no auth, loopback only)
[dsh-auth-access proxy] patch enabled (DSH_WEB_AUTH_PATCH=1) # when enabled
[dsh-auth-access proxy] patch failed: ... does not contain PATCH_FROM # on MITM miss
[dsh-auth-access proxy] failed to listen on [::]:3082: EADDRINUSE ... - proxy not started
Development
pnpm install
pnpm build
pnpm typecheck
pnpm publish --access public
Security notes
- Plain HTTP — cookies/TOTP can be sniffed; use on trusted LAN / Tailscale only
127.0.0.1:3080 remains loopback-only; public surface is only ::3082 when password is set
- Removing the plugin restores vanilla
dsh web (dsh plugin --profile web remove @pananfly/dsh-auth-access)
License
MIT