dsh-remote-trust
其他 活跃维护

dsh-remote-trust

aashizpoudel/dsh-remote-trust

作为对应主工具的扩展插件,可将配置修改、凭证管理、模型发现等高权限API接口开放给已配置的可信远程主机,支持跨可信节点安全调用敏感接口,无需本地登录即可远程完成配置操作。

0
Stars 标星
0
Forks 分支
0
Watchers 关注
0
Open Issues
JavaScript
主要语言
MIT
开源协议
14 KB
仓库大小
1 个月前
最后推送
一键安装扩展 / 插件指令
dsh plugin --profile web add github:aashizpoudel/dsh-remote-trust
git clone https://github.com/aashizpoudel/dsh-remote-trust.git
git clone git@github.com:aashizpoudel/dsh-remote-trust.git
README.md main

@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:

  1. A general fence — the request's Host must be loopback or a
    --trusted-host authority, with Origin and sec-fetch-site checks.
  2. A privileged-method pin — settings.*, credentials.*,
    agentPreset.*, and llm.discoverModels are additionally FORCED to
    loopback with an empty trust list, even when --trusted-host names 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:

  • Host must be loopback or a configured authority (DNS-rebinding gate);
  • Origin, when a browser sends one, must match the Host;
  • sec-fetch-site: cross-site is 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-apiproxy to a matching version so the forwarded
    bridge stays compatible. After upgrading dsh, bump that dependency.
  • It opens, it does not delete. Deleting a privileged method still needs a
    cordis.yml composition 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