TOTP-only access control for personal DeepSeek Harness Web instances, with in-app QR enrollment, single-use recovery codes, live protection controls and lock-all revocation of page access.
Install
# from npm (prebuilt)
dsh plugin --profile web add dsh-totp
# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)
dsh plugin --profile web add github:SodaZheng/dsh-totp
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
TOTP access control for personal DeepSeek Harness Web instances.
简体中文 · English

Enter the six-digit code from your authenticator before accessing your DSH workspace. The plugin supports standard TOTP apps such as Google Authenticator and Microsoft Authenticator. Enrollment, protection controls, recovery codes, and locking are all handled inside DSH.
Built for people who want access control for a personal DSH Web instance, especially when accessing it over a LAN or remotely. TOTP is the sign-in credential: no Google / Microsoft account sign-in or separate login service is required.
Compatibility target: DSH 0.1.2-rc.1 · Node.js 24+ · MIT License
Quick start · Everyday use · How it works · Security boundaries
Why use it?
| What you need | What dsh-totp provides |
|---|---|
| Knowing the URL should not grant access | With protection enabled, localhost, LAN, and public access through the gateway require a code |
| No extra account system or login site to maintain | QR enrollment inside DSH Settings |
| Control over when verification is required | Protection starts off on a fresh installation, turns on after enrollment, and can be toggled later |
| An immediate way to revoke access when stepping away | Lock all authorized pages and devices with one click, without another code |
| A way back after losing access to a phone | Single-use recovery codes and an authenticator replacement flow |
| No second login immediately after enrollment | The enrolling page stays usable; save the recovery codes and continue |
Quick start
The plugin's UI labels are currently Chinese. English descriptions below include the key Chinese labels where needed.
1. Add the plugin
Install the published npm package:
dsh plugin --profile web add dsh-totp
The package is available on the public npm registry. If your mirror has not synced it yet, specify the official registry:
dsh plugin --profile web add dsh-totp --registry=https://registry.npmjs.org
To develop or check the current source, run from this repository's directory:
npm ci
dsh plugin --profile web add "$PWD"
Plugins are installed per profile. These commands target web; replace it with your profile name if you use another Web profile.
This plugin replaces the profile's WebServer and takes over its external HTTP entry. Do not combine it with another plugin that replaces the WebServer or owns the login flow in the same profile. It ships JavaScript directly, needs no build, and has no install-time scripts. Ordinary packaging does not run tests or create enrollment data.
2. Start DSH
If the Web instance is already running, stop it and start it again once to load the plugin configuration:
dsh --profile web --no-open
Open the address printed after dsh-totp HTTP entry in the terminal.
Protection is off on a fresh installation. Enter DSH first, then enroll your authenticator. Upgrades, reinstalls, and ordinary restarts preserve the existing enrollment and protection setting rather than resetting them to the defaults.
3. Enroll an authenticator
- Open Settings → Access Verification (
设置 → 访问验证) and select Bind authenticator (绑定验证器). - Add an account in Google Authenticator or Microsoft Authenticator and scan the QR code.
- Enter the new authenticator's current six-digit code, then select Confirm enrollment and enable protection (
确认绑定并开启保护). - Save the 10 single-use recovery codes and select Saved, continue using DSH (
已保存,继续使用 DSH).
Protection turns on immediately after confirmation. The enrolling page stays usable and returns directly to Settings. No second login or wait for the next code is required. Existing authorization on other open pages is revoked.
Generating a QR code or canceling enrollment does not enable protection. Enrollment and replacement are performed in the browser; there is no CLI enrollment command.
Everyday use
| Action | How | Result |
|---|---|---|
| Enter DSH | With protection on, open or refresh the page and enter a code | A temporary authorization is issued for that page |
| Enable / disable protection | Use the Settings control and confirm with a fresh code | Takes effect immediately and revokes old page authorization; access is direct when protection is off |
| Lock all devices | Select Lock (锁定) on the conversation page, or Lock all devices (锁定所有设备) in Settings |
Locks the current page and all other authorized pages without another code |
| Regenerate recovery codes | Select Update recovery codes (更新恢复码) and enter a fresh code |
Issues 10 new codes and invalidates the old set |
| Replace an authenticator | Verify a current code, then scan a new QR code and confirm the new code | Activates the new authenticator; the current page stays usable and other pages must verify again |
Lock button states:
- No authenticator enrolled: disabled, with a prompt to enroll in Settings.
- Enrolled, protection off: disabled, with a prompt to enable protection in Settings.
- Enrolled, protection on: ready to lock immediately, without another verification step.
Locking closes access connections. The plugin does not explicitly cancel DSH background tasks; their behavior after disconnection depends on DSH and the plugins running those tasks.
When your phone is unavailable
Select Cannot use authenticator (无法使用验证器) on the login page, enter an unused recovery code, and enroll a new authenticator.
A successful recovery-code check immediately revokes the old authenticator and old page authorization. Until the new authenticator is confirmed, the recovery capability permits enrollment only, not access to DSH data. Confirm the new TOTP, save the new recovery codes, and enter DSH directly without another login.
Each recovery code can be used once. Replace your saved backup whenever you regenerate codes or complete a new enrollment.
How it works
The plugin places a single access gateway in front of DSH's Web server and makes authorization decisions on the server. This diagram shows the request path with protection enabled:
flowchart LR
A["Authenticator<br/>Generates six-digit TOTP"] -. "User reads and enters code" .-> B["Browser<br/>Localhost / LAN / Internet"]
subgraph H["Host running DSH"]
G["dsh-totp<br/>Access gateway"]
V{"Valid page authorization?"}
L["Show login page<br/>Reject protected data requests"]
W["DSH WebServer<br/>127.0.0.1 · Dynamic internal port"]
D[("Local state<br/>Enrollment / Limits / Recovery hashes")]
G --> V
V -- "No" --> L
V -- "Yes" --> W
G <--> D
end
B -- "HTTP / WebSocket" --> G
- One entry point. The plugin replaces the original listener configuration. Its gateway handles external access, while the internal DSH WebServer is bound to loopback.
- One process and one access address. The plugin runs inside DSH. Enrollment and management use the same external port; no separate enrollment service is started.
- Server-enforced authorization. Pages carry temporary credentials for API and WebSocket access. A hidden button or login screen is not the security boundary.
- Native credentials stay on the server. The gateway completes DSH's native authentication and proxies requests without sending those native tokens or cookies to the browser.
- Enrollment continues the current session. Confirming the new TOTP updates authorization for the enrolling page and revokes authorization for other pages.
When protection is off, the gateway permits direct access to DSH. The toggle controls actual access, not just the appearance of the login page.
LAN and remote access
Allow other devices to connect using the host's IP address:
dsh --profile web --host 0.0.0.0 --port 3080 --no-open
For a domain, public address, or port mapping, also declare the host:port that browsers will use. For example:
dsh --profile web --host 0.0.0.0 --port 3080 --no-open \
--trusted-host dsh.example.com:3080
Replace the example domain. --trusted-host checks the request's destination Host, not the client's source IP. It does not configure DNS, firewall rules, or router port forwarding. Local IPv4 interface addresses are added automatically to the allowed access addresses.
With protection enabled, localhost, LAN IPs, and public access through the gateway follow the same TOTP rules.
Verification rules
| Item | Current behavior |
|---|---|
| Codes | Standard TOTP: HMAC-SHA-1, six digits, 30-second period, ±1-step time tolerance |
| Replay protection | A time step is accepted only once per authenticator; consumption records survive restarts |
| Per-source failure limit | Up to 5 failures per 5-minute window, per source IP as seen by the server |
| Global failure limit | Up to 10 failures per 1-minute window across all sources |
| Page authorization | Expires after 15 minutes without user activity, with an 8-hour maximum lifetime by default |
| Enrollment flow | Valid for 5 minutes |
| Recovery codes | 10 single-use codes; regeneration or successful re-enrollment invalidates the previous set |
Login, recovery, and TOTP-protected management operations share the failure allowance. Successful checks, formatting errors, and replayed codes do not consume it. A successful check does not erase previous failures. The UI displays a countdown when a limit is reached.
What if a code has already been used? An already authorized DSH page remains usable. Wait for the next code when entering another page or performing another operation that requires one. Completing enrollment does not require that wait.
Security boundaries
HTTP support does not provide transport encryption. With HTTP, an attacker on the network path may capture or modify QR codes, TOTPs, page credentials, and DSH data. TOTP access control does not replace HTTPS or another protected transport, and is not phishing-resistant.
Use the plugin within these boundaries:
- It provides TOTP access verification for a personal instance, not multiple user accounts, roles, or password-plus-TOTP two-factor authentication.
- The gateway handles HTTP directly. It does not terminate TLS or adapt Origin checks for HTTPS reverse proxies; use a protected network or tunnel for remote access.
- With protection off, anyone who can reach the address can enter DSH and use the file, command, and other capabilities available in that instance.
- TOTP seeds are encrypted locally with AES-256-GCM and a separately stored master key. This does not defend against an attacker with host access who can read both the database and that key.
- The internal loopback listener reduces network exposure but does not isolate local processes. The plugin is not a workspace isolation layer or an execution sandbox.
- Current integration covers DSH Fetch, XHR,
/api/WebSockets, and scoped resource requests. Custom connection paths used by third-party plugins may need additional integration. - Corrupt state, a missing key, or database failures deny access. Uninstalling the plugin removes this access-control layer; it is not equivalent to disabling protection in Settings.
Configuration and local administration
Optional configuration
Configure the plugin in the relevant profile's cordis.patch.yml:
- id: dsh-totp
config:
host: '0.0.0.0'
port: 3080
allowedHosts:
- 'dsh.example.com:3080'
idleMs: 900000
maxMs: 28800000
Listener, port, and data-directory changes require a restart. In-page protection toggles and locking take effect immediately.
DSH validates configuration through the plugin's exported Config. host accepts 127.0.0.1 or 0.0.0.0; port is an integer from 0 to 65535 (0 chooses a dynamic port). idleMs must not exceed maxMs; both must be positive integers no greater than 24 hours. allowedHosts accepts host[:port] values without a scheme or path.
Data directory
The default resolves from DSH_TOTP_DATA_DIR, then $DSH_HOME/dsh-totp, then ~/.dsh/dsh-totp. The plugin's dataDir configuration can override that default.
Only one running plugin instance may use a data directory. Use separate directories for separate DSH instances. Ordinary restarts preserve enrollment and the protection setting, but not page authorization.
Local commands
Run from this repository's directory:
node src/cli.js status
node src/cli.js doctor
node src/cli.js enable
node src/cli.js disable
node src/cli.js lock
These are recovery and administration tools for someone with local management privileges; they do not require a phone code. The lock command enables protection and locks all pages, unlike the Web button that is disabled while protection is off. Protection cannot be enabled without an enrolled authenticator.
Add --data-dir /path/to/data when needed. The CLI and plugin must use the same data directory. These commands do not enroll or replace authenticators.
Check and package from source
npm ci
npm run verify
npm pack
check validates JavaScript syntax and runs tests for TOTP, persistence, replay protection, rate limits, recovery, page authorization, the HTTP / WebSocket gateway, and the release script. Tests use temporary directories, loopback ports, and a simulated upstream. They do not read your DSH data or publish to npm or GitHub.
verify runs check and check:package. The latter parses bundle YAML and validates versions, package contents, runtime entries, local imports, and client registration of the settings slot under the package name. yaml is a development-only dependency.
npm pack creates the archive directly; npm publish runs verify automatically through prepublishOnly. The package includes runtime source, plugin configuration, READMEs, the cover and its notes, and the license; local dependencies and test data are excluded.
GitHub Actions is configured to run both checks on Node.js 24 on Linux, macOS, and Windows. The automated gateway tests use a simulated DSH upstream and do not replace browser interaction checks in the target DSH environment.
The cover is a conceptual illustration. The architecture diagram describes this repository's implementation. After upgrading DSH, verify enrollment, login, protection controls, and locking in your target environment.
See submission notes and review evidence for the listing entry, and the source comparison with related plugins for implementation decisions.
License
Links
More in this category
toby-bridges/api-relay-audit★ 832
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.
howmp/dsh-pentest★ 451
Authorized pentest mode for DeepSeek Harness — exploration chain, assets and findings with a Web view.
SeaOf0/dsh-redteam-model★ 417
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.
PerryLink/dsh-auto-review★ 164
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.
PerryLink/dsh-permission-rules★ 114
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.
PensiveFei/dsh-secure-audit★ 85
Read-only security and compliance plugin for DeepSeek Harness: prompt-injection detection, Chinese-PII redaction, and a local configuration audit with redacted, reproducible reports.
Community comments
Comments are public GitHub Discussions. Loading them connects to GitHub and Giscus; a GitHub account is required to post.