dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
A UI-level access gate and process-wide agent tool brake for
DeepSeek Harness (dsh).
Log in once → every open DSH page unlocks. Nobody logged in → the agent itself can't run a single tool.
DSH's /api surface has no authentication of its own — that is a deliberate
property of the harness, not a bug this plugin fixes. The harness's own source
describes its request fence like this:
trustedHostsis a DNS-rebinding fence, explicitly not authentication
Any client that can reach the DSH port can bypass this plugin entirely by calling the HTTP API directly. This plugin gates:
tools.guard brake)It does not gate the HTTP surface. It is not a reverse proxy, not a session layer, and not an authorization system.
Always pair it with a real boundary:
| Real boundary | What it gives you |
|---|---|
Bind DSH to loopback (dsh web, the default) | Nothing on the network can reach it at all |
| A VPN / overlay (WireGuard, Tailscale, ZeroTier) | Only your own devices can route to the port |
| A TLS reverse proxy that performs real authentication | The only place a real auth layer can live |
If you deploy this on a public port and believe you are protected, you are worse off than having nothing, because you will stop looking for the real problem.
webserver/index-inject channel — no client bundle, no module
loader, nothing to keep in sync with the SPA.ctx.tools.guard
denies every agent tool call. The agent cannot read files, run commands, open
the network, or spawn subagents.~/.dsh/permission-gate.json (mode 0600). The session lives in memory
only, so a restart always requires a fresh login — but your password is never
reset./gate is plain HTML served by the host
half. It works even if the injected script fails or is cached out.权限闸门 page inside DSH's own Settings,
where the gate can be disabled — and disabling requires the password.localStorage.This package declares dsh.bundle, so it is a profile layer: installing it is one
command, and DSH reconciles the composition for you. There is no patch file to edit.
dsh plugin --profile web add dsh-permission-gate
# restart DSH once — the client roster is scanned at startup
The bundle inserts this row on its own (see cordis.patch.yml):
- insert:
- id: permission-gate
name: dsh-permission-gate
Migrating from a manual install? Delete that same row from your own
~/.dsh/profiles/web/cordis.patch.ymlfirst. The bundle now inserts it, and two entries sharing oneidwill collide.
On the next page load you get the lock page. The first visit asks you to set a password; every visit after that asks you to enter it.
All of them are served by the host half and sit behind the same origin as DSH.
| Method | Path | Purpose |
|---|---|---|
GET | /gate | Server-rendered login page. Always available. |
GET | /gate/ui.js | The injected browser script (cache-control: no-store). |
GET | /gate/status | { state, expires, remainingMs, idleMs, hasCredential, authenticated } |
POST | /gate/login | { password, mode } — mode: "setup" on first run. Sets the session cookie. |
POST | /gate/touch | "A human is present" — extends the idle clock. |
POST | /gate/lock | Drop the session and clear the cookie. |
POST | /gate/mode | { password, enable } — disable/enable the gate. Requires the password. |
state is one of setup · locked · unlocked · disabled.
| Scenario | Covered? |
|---|---|
| Someone picks up your unlocked phone/laptop and opens the browser | ✅ full-screen lock page |
| You walk away and an autonomous agent keeps working | ✅ strict idle lock + tool brake |
| A page is left open on a second device | ✅ shared session logic; all pages mirror the server state |
Someone in your tailnet calls /api directly | ❌ not covered — that is what the real boundary is for |
Someone can edit cordis.patch.yml or the package directory | ❌ not covered — a plugin cannot defend the composition that loads it |
| Someone restarts the DSH process | ❌ not covered — the gate lives in the process; a restart also clears the session |
Three tools stay callable while the gate is locked, because they touch no files, commands, session content, or credentials:
get_goal · update_goal · todo_write
They exist so a locked agent can still report state and close out its own
bookkeeping. Remove the BOOKKEEPING_TOOLS line in lib/index.js if you want
a zero-exemption gate.
| Situation | What to do |
|---|---|
| Forgot the password | Delete ~/.dsh/permission-gate.json → next visit asks you to set a new one |
| The injected UI is broken / cached | Visit /gate directly — it is server-rendered |
| Locked out completely | Set disabled: true on the row in cordis.patch.yml, or remove the row. cordis.patch.yml is watched live, so no restart is usually needed |
| Want to inspect state | curl http://127.0.0.1:3080/gate/status |
Built while running DSH remotely from a phone. Three bugs are baked into the comments and the test suite, because each one is a trap anyone building injected UI for DSH will hit:
appendChild(null) when the
"confirm password" field is absent) — the host-side guard kept working while
the UI showed nothing, i.e. "no permissions, but you can still chat."
Fix: never append null, and wrap the whole render in a fallback that puts
the error on screen./gate/touch
originally demanded a cookie matching the session token. After a restart only
the browser that logged in held a valid cookie, so every other page's activity
was rejected with 401 — the countdown only ever went down. Fix: touch
grants nothing; it only requires that the shared session is unlocked.The smoke test runs the real lib/ui.js against a minimal DOM stub whose
appendChild throws on null, exactly like a real DOM. No browser needed.
npm test
Developed against DSH 0.1.1-rc.2 / cordis 4.x, and depends on these
harness capabilities:
webServer.register · webserver/index-inject · tools.guardsettings.section slot · slots and timer servicesThese are internal-ish APIs; a DSH upgrade may require a matching update here.
MIT