Files
deepseek-harness/docs/user/guide/network-proxy.md
T
Yichen Jiang cfc9b3bdef fix(net): route by the policy, and give a child the routing its parent has
Second review pass on the outbound proxy work.

The installed dispatcher was undici's EnvHttpProxyAgent, which reuses the HTTP
proxy for `https:` whenever no HTTPS proxy is present. That is exactly the state
this package resolves after refusing a SOCKS or malformed URL the user named for
`https:`, so the scheme the diagnostic reported as direct was tunnelled anyway.
The dispatcher is now an Agent whose per-origin factory calls `proxyForUrl`, so
routing and `proxyForUrl` cannot disagree by parsing the same list twice.

`childProxyEnv` returned only the names the user exported, which left a child
Node direct whenever the proxy came from `ALL_PROXY` or from cordis.yml — Node's
`NODE_USE_ENV_PROXY` reads neither — and stripped the merged loopback bypass so
the child sent its own localhost traffic to the proxy. A scheme the user named
in either casing still reaches the child exactly as written; one they named in
neither now carries the resolved value, and the bypass list is always the merged
one.

A nested install (the plugin mounted over the launcher's policy) recorded the
outer policy's published values as the user's, then cleared the record on
disposal, so every later child inherited the normalization instead. The record
now belongs to the outermost install and is restored, not dropped.

`web_fetch` read the active policy twice — once to skip address pinning, again
inside the transport — so a disposal landing between the two reads produced an
unpinned direct connection to a host nothing validated. One snapshot now decides
both.

Also: the node:http proxy test asserted a route the engines range does not always
have, and the gate could not see undici bound through `await import('undici')`,
the form this repository actually uses.
2026-08-29 13:17:12 +08:00

3.9 KiB

Run DSH behind a network proxy

English | 中文

DSH routes every outbound request — model calls, web search, page fetches, MCP servers over HTTP, and telemetry — through the proxy named by the standard proxy environment variables. It reads them at launch; nothing else needs configuring.

Export the variables

export HTTPS_PROXY=http://127.0.0.1:7890
export HTTP_PROXY=http://127.0.0.1:7890

Put both lines in your shell profile so every dsh invocation inherits them. DSH also reads a .env file in the launch directory and in $DSH_HOME, so a proxy that should apply to one project can live there instead; a real environment variable always wins over a file.

A proxy that needs credentials takes them in the URL: http://user:password@proxy.example:8080. DSH never prints the password back — a proxy it reports in a diagnostic shows the username and masks the rest.

Why your browser is proxied but your terminal is not

This is the most common surprise, and it is not specific to DSH. There is no single "system proxy" that all software obeys — there are three unrelated mechanisms:

Mechanism Who follows it
The operating system's proxy settings Safari, most native macOS apps, Chrome and Edge
The HTTP_PROXY / HTTPS_PROXY environment variables curl, git, npm, pip, and DSH
TUN mode (a virtual network interface) Everything, transparently

The "system proxy" switch in a proxy application such as Clash writes only the first one. Browsers pick it up; command-line tools never see it. That is why exporting the variables is a separate step, and why turning on TUN mode makes both work without any variables at all.

DSH does not read the operating system's proxy settings. Export the variables, or use TUN mode.

Choose what stays direct

NO_PROXY lists hosts to reach directly:

export NO_PROXY=internal.example.com,.corp.example.com,registry.local

An entry matches an exact host, a .suffix or *.suffix domain, an optional :port, or * for everything.

CIDR ranges do not work. An operating system bypass list often contains entries like 10.0.0.0/8 or 192.168.0.0/16; copying those into NO_PROXY has no effect. Use host names or domain suffixes instead.

You do not need to list localhost or 127.0.0.1. DSH always bypasses loopback, because its own Web UI and local servers would otherwise route through the proxy and loop.

Limits worth knowing

SOCKS proxies are not supported. A socks5:// value is reported at startup and skipped, and DSH connects directly for the scheme that named it — setting HTTPS_PROXY=socks5://… alongside a usable HTTP_PROXY leaves https: direct rather than borrowing the HTTP proxy. Point the variables at your proxy application's HTTP port instead — most expose both, and the HTTP one is usually a neighbouring port number.

ALL_PROXY alone is enough. DSH falls back to it for both schemes, even though Node and curl differ on this. Setting HTTPS_PROXY explicitly is still clearer.

A TLS-intercepting corporate proxy needs its certificate. If requests fail with a certificate error once the proxy is reachable, point Node at your organisation's CA bundle before launching:

export NODE_EXTRA_CA_CERTS=/path/to/corporate-ca.pem

Node reads that variable only at process start, so export it before running dsh.

Tools DSH runs for you follow the same proxy. Commands in the bash tool, git, gh, and MCP servers started as child processes all inherit these variables. A child that is itself a Node program honors them only on Node 22.21 or later; an older Node connects directly.

Check that it worked

Ask the agent to fetch a page and watch your proxy application's connection log:

dsh --profile headless "fetch https://example.com and tell me the page title"

If the request does not appear there, confirm the variables survive into DSH's own environment:

env | grep -i proxy