aashizpoudel
dsh-remote-trust
DSH plugin: open privileged /api methods (settings, credentials, model discovery) to remote trusted hosts
- Stars
- 0
- Language
- JavaScript
- Created
- Aug 16, 2026
- Updated
- Aug 16, 2026
Introduction
@crckss/dsh-remote-trust
Open DSH's privileged /api methods to remote trusted hosts, so a browser
visiting the Web UI through a reverse proxy (or from another machine on your
network) can configure the harness — Settings → Models, provider and
credential management — instead of getting 403 forbidden.
# ~/.dsh/profiles/web/cordis.patch.yml
- insert:
- id: remote-trust
name: "@crckss/dsh-remote-trust"
config:
hosts:
- dsh.example.com
Why this exists
DeepSeek Harness binds its Web server to loopback for safety (--host 0.0.0.0
is refused outright) and its /api browser-trust fence has two layers:
- A general fence — the request's
Hostmust be loopback or a--trusted-hostauthority, with Origin andsec-fetch-sitechecks. - A privileged-method pin —
settings.*,credentials.*,agentPreset.*, andllm.discoverModelsare additionally FORCED to loopback with an empty trust list, even when--trusted-hostnames your domain.
That pin is hardcoded in @deepseek-ai/dsh-client-connection, so a deployment
behind Traefik/Caddy/nginx can browse sessions but can never open the settings
plane. This plugin removes the pin for endpoints you declare, without
touching node_modules.
How it works
The webserver's route table is checked exact-first, then longest-prefix.
The built-in /api route is a prefix route, so this plugin registers
exact routes for each privileged endpoint (/api/settings.describe, …).
Those exact routes win, run the same browser-trust fence (Host + Origin +
sec-fetch-site) against loopback plus your configured hosts, and then
forward the request to apiProxy exactly like the built-in bridge would.
The two host-native methods (host.pickDirectory, host.openPath) are not
opened by default because they drive native dialogs on the server machine.
Install
Manually (local profile)
# from your DSH profile directory
cd "${DSH_HOME:-~/.dsh}/profiles/web" # or profiles/tui, …
# pnpm must be on PATH (dsh plugin forwards to pnpm)
dsh plugin --profile web add @crckss/dsh-remote-trust
# or, from a local checkout:
dsh plugin --profile web add /path/to/dsh-remote-trust
From npm
dsh plugin --profile web add @crckss/dsh-remote-trust
Configuration
- insert:
- id: remote-trust
name: "@crckss/dsh-remote-trust"
config:
# Authorities allowed to call privileged methods.
# Port-less host matches any port; host:port matches exactly.
hosts:
- dsh.example.com
- 100.34.48.118
# Optional: open privileged methods beyond the defaults
# (these are host-native actions, opt-in).
extraEndpoints:
- host.pickDirectory
# Optional: request body ceiling for the forwarded bridge.
maxRequestBodyBytes: 4194304
Trusted authorities are merged from two places:
config.hosts— explicit entries in the patch layer;ctx.webStartup.trustedHosts— whatever the invocation declared with--trusted-host, plus LAN IP literals derived from the bind (when bound to all interfaces).
So the plugin follows the deployment's existing trust declaration: if you
already run dsh web --trusted-host dsh.example.com, the plugin picks that up
even with an empty hosts list.
Opened by default
agentPreset.read agentPreset.copy
agentPreset.openDocument agentPreset.remove
settings.describe settings.openDocument
settings.update settings.replace
settings.mutate
credentials.describe credentials.set
credentials.unset
llm.discoverModels
host.pickDirectory and host.openPath stay loopback-only unless listed in
extraEndpoints.
Security model
The plugin reuses the harness's own browser-trust logic — it does not add a looser check:
Hostmust be loopback or a configured authority (DNS-rebinding gate);Origin, when a browser sends one, must match theHost;sec-fetch-site: cross-siteis always refused.
Nothing becomes unauthenticated: the fence is not authentication, and it
never was. What changes is that the config plane stops being loopback-only and
becomes reachable from the authorities you already trust with
--trusted-host.
Do not expose the DSH port (3080) or the whole machine through a public proxy unless you add your own authentication. The fence protects against rebinding and cross-site requests, not against a stranger who can reach the port.
Verify
BODY='{"type":"client-request","rpcId":"v","method":"settings.describe","payload":{}}'
# remote host -> should be 200 (was 403 without the plugin)
curl -i -H 'Host: dsh.example.com' -H 'Content-Type: application/json' \
-d "$BODY" http://127.0.0.1:3080/api/settings.describe
# cross-site / attacker host -> must stay 403
curl -i -H 'Host: dsh.example.com' -H 'Origin: https://evil.example' \
-H 'sec-fetch-site: cross-site' -H 'Content-Type: application/json' \
-d "$BODY" http://127.0.0.1:3080/api/settings.describe
curl -i -H 'Host: evil.example' -H 'Content-Type: application/json' \
-d "$BODY" http://127.0.0.1:3080/api/settings.describe
Known limitations
- Tracks the harness release. The pin lives in
@deepseek-ai/dsh-client-connection; this plugin pins@deepseek-ai/dsh-host-apiproxyto a matching version so the forwarded bridge stays compatible. After upgradingdsh, bump that dependency. - It opens, it does not delete. Deleting a privileged method still needs a
cordis.ymlcomposition change; the settings seam's merge semantics apply. - No auth added. Add your own if the endpoint can be reached by people you do not trust.
License
MIT