Back to home

roojay

dsh-trusted-host-proxy-403-fix

No description

Stars
0
Language
JavaScript
Created
Aug 14, 2026
Updated
Aug 15, 2026

Introduction

dsh-trusted-host-proxy-403-fix

English | 中文

A standalone DeepSeek Harness plugin bundle for the web profile. Compatible with @deepseek-ai/dsh@0.1.0-rc.6.

dsh web (dsh --profile web) listens on the loopback interface. You can repeat --trusted-host to add trusted host entries that pass the /api browser request security checks. Requests to non-privileged API methods then succeed when their Host matches one of those entries.

The Node.js /api handler applies a second check to the privileged method set with an empty trustedHosts list, which restricts these methods to loopback. As a result, a reverse proxy can reach other API methods through a configured host, while these 15 methods remain unavailable. When the proxy preserves the public Host header, Settings → Models fails with:

transport failure for /api/settings.describe: HTTP 403

This plugin registers an exact /api/<method> route for each privileged method and validates those requests against the same trustedHosts list as the rest of /api. It keeps the official connection plugin in place and does not provide authentication. The official host checks control which hostnames may reach the API; they do not verify user identity. The Web transport has no built-in authentication.

Before you install

After installation, requests using any host listed in trustedHosts can call the methods below. These methods can read and change settings and credentials, open native directory dialogs, manage agent presets, and make the DSH host send a model discovery request to a URL supplied by the caller.

Install this plugin only when those hosts are already protected by authentication or equivalent network isolation.

  1. Add only the hosts that clients use to --trusted-host. Each entry must be a canonical host[:port] value without a scheme, path, user information, or wildcard.
  2. Protect those hosts with authentication or network isolation. Do not expose dsh web directly to the public internet with this plugin as the only protection. The official CLI still rejects --host 0.0.0.0.
  3. For browser requests, the host and port in Origin must match the Host header. Requests with sec-fetch-site: cross-site or Origin: null are rejected.
  4. Do not rewrite Host to a loopback address while keeping the public HTTPS origin. That mismatch also causes the rest of /api to return 403.
  5. User authentication and authorization remain outside this plugin.

Privileged methods

This is the complete official PRIVILEGED_METHODS list in @deepseek-ai/dsh-client-connection 0.1.0-rc.6. All other /api routes keep the default dsh-web-app behavior.

MethodPurpose
/api/settings.describeRead all exposed settings namespaces
/api/settings.openDocumentOpen the settings document
/api/settings.updateUpdate settings
/api/settings.replaceReplace a settings document
/api/settings.mutateModify settings
/api/credentials.describeCheck whether a credential is configured and where it comes from
/api/credentials.setSave a credential
/api/credentials.unsetRemove a credential
/api/llm.discoverModelsSend a model discovery request from the DSH host using credentials supplied by the caller
/api/host.pickDirectoryOpen the native directory picker
/api/host.openPathOpen a path on the DSH host
/api/agentPreset.readRead an agent preset definition
/api/agentPreset.copyCopy an agent preset
/api/agentPreset.removeRemove an agent preset
/api/agentPreset.openDocumentOpen an agent preset document

The following methods are not part of this list and remain unchanged: /api/llm.providers, /api/llm.models, /api/host.describe, /api/agentPreset.list, /api/agentPreset.select, session.create, /api/events.mux, and /api/events.host.

Release an updated plugin version if the official privileged method list changes.

Settings persistence for trusted hosts

The 403 fix above unblocks the privileged RPCs, but the browser still treats a non-loopback page as untrusted and runs every settings namespace (Language, Appearance, Composer Enter, ...) in "memory" persistence mode: choices apply for the current page and are silently dropped on reload.

This package's browser half upgrades those controllers to host persistence when the privileged RPCs are reachable:

  • It patches SettingsScopeController.prototype.enqueue so the "memory" short-circuit stops swallowing reads and writes. Without the server half installed (or for an untrusted host) the RPC fails with 403 and the official controllers keep their fail-closed catch-and-ignore behavior, exactly as before — installing this package changes nothing on deployments where it is not installed.
  • It upgrades the controllers reachable through the locale and theme services in place (persistence is a plain instance field) and triggers a reload, so a saved preference applies on the first paint after the upgrade without waiting for the user to pick it again.

The browser half is a dsh.client entry shipped from the same row: no additional configuration is needed. dsh.client.inject uses the official package names (@deepseek-ai/dsh-client-locale, @deepseek-ai/dsh-client-ui-theme, @deepseek-ai/dsh-client-ui-settings) so the composed graph can require SettingsScopeController. The enqueue patch is pinned to @deepseek-ai/dsh@0.1.0-rc.6; re-check the official controller if you upgrade dsh.

After installing, the Language (and Appearance, Composer Enter) preferences are written to the settings document (e.g. settings.yaml under the harness home) and survive page reloads and Web process restarts.

Install

This package uses ESM and has no prepare script, so installing it from Git does not require allowBuilds.

dsh plugin --profile web add github:roojay/dsh-trusted-host-proxy-403-fix#v0.2.0

Install from npm:

dsh plugin --profile web add dsh-trusted-host-proxy-403-fix@0.2.0

Install from a GitHub Release tarball:

dsh plugin --profile web add https://github.com/roojay/dsh-trusted-host-proxy-403-fix/releases/download/v0.2.0/dsh-trusted-host-proxy-403-fix-0.2.0.tgz

Install from a local directory:

cd /absolute/path/to/dsh-trusted-host-proxy-403-fix
pnpm install --ignore-scripts
dsh plugin --profile web add "$PWD"

The dependency installation is required for a local-directory link. Without it, DSH cannot resolve the plugin's peer dependencies when the Web process starts.

dsh web is equivalent to dsh --profile web. Restart the Web process after installation and continue passing the public host when starting it:

dsh web --port 3080 --trusted-host app.example.com

Do not hard-code a host in cordis.patch.yml. Keep trustedHosts set to !!js ctx.webRuntime.trustedHosts.

Remove the plugin:

dsh plugin --profile web remove dsh-trusted-host-proxy-403-fix

Verify

dsh --profile web --dump-config

The merged configuration should contain a # == dsh-trusted-host-proxy-403-fix section. Its trusted-host-proxy-403-fix entry should read trustedHosts from ctx.webRuntime.trustedHosts.

Send a request to the loopback address using the same host passed to --trusted-host:

# before: 403 forbidden
# after:  200 (the RPC response may be invalid-request, but it must not be forbidden)
curl -sS -D- -o /tmp/dsh-body -X POST http://127.0.0.1:3080/api/settings.describe \
  -H 'Host: app.example.com' \
  -H 'Origin: https://app.example.com' \
  -H 'content-type: application/json' \
  -d '{}'

A mismatched Origin must still be 403:

curl -sS -o /dev/null -w '%{http_code}\n' -X POST http://127.0.0.1:3080/api/settings.describe \
  -H 'Host: app.example.com' \
  -H 'Origin: https://evil.example' \
  -H 'content-type: application/json' \
  -d '{}'
# expect 403

After passing the authentication configured in front of DSH, open Settings → Models in the browser. The privileged method should no longer return 403.

Then confirm settings persistence (the 0.2.0 browser half):

  1. Settings → Language: pick zh or en and save.
  2. Check $DSH_HOME/settings.yaml contains locale.preference with that value.
  3. Hard-refresh the page. The language choice must still be there.
  4. Restart dsh web and open the page again. The choice must still be there.

How it works

dsh-host-webserver checks exact routes before prefix routes. This plugin registers one exact /api/<method> route for every privileged method, so those requests bypass the empty trustedHosts check in the official /api handler.

The plugin is pinned to @deepseek-ai/dsh@0.1.0-rc.6. It depends on the official connection service and creates its Fetch handler with createSharedFetchHandler, so its privileged routes are removed whenever the official /api route is removed. API_PATH and Config come from official exports. The request security logic and privileged method list are copied locally because rc.6 does not export them. The request body limit matches the official 160 MiB default, and errors propagate to the official WebServer error handler.

Develop

npm test

License

MIT. The request validation logic is based on @deepseek-ai/dsh-client-connection.