Bundle
@jiachi/dsh-remote-gateway
Source-filtered remote gateway for DeepSeek Harness behind an authenticated reverse proxy
- Source
- Jiachi5533
- License
- MIT
- Updated
- Updated yesterday
Readme
<p align="center">
<img src="assets/icon.svg" width="96" alt="DSH Remote Gateway icon">
</p>
<h1 align="center">DSH Remote Gateway</h1>
<p align="center">
A source-filtered gateway for using DeepSeek Harness through an authenticated reverse proxy.
</p>
<p align="center">
<a href="README.zh-CN.md">简体中文</a> ·
<a href="#security-model">Security</a> ·
<a href="#compatibility">Compatibility</a>
</p>
<p align="center">
<img alt="Tests" src="https://img.shields.io/github/actions/workflow/status/Jiachi5533/dsh-remote-gateway/ci.yml?branch=main&label=tests">
<img alt="Node.js 22.19 or newer" src="https://img.shields.io/badge/node-%3E%3D22.19-339933">
<img alt="MIT License" src="https://img.shields.io/badge/license-MIT-6e56cf">
</p>

## Why
DSH intentionally keeps privileged browser capabilities on loopback. A normal reverse proxy can render the page remotely while settings, plugin bundles, WebSockets, or host directory selection still fail. This plugin provides a narrow compatibility gateway without exposing the internal DSH web server directly.
## Features
- Proxies HTTP, SSE, and WebSocket traffic.
- Accepts requests only from explicitly listed source IP addresses.
- Keeps the internal DSH web server bound to `127.0.0.1`.
- Rewrites the upstream `Host` and `Origin` to the internal loopback authority.
- Strips edge authentication and forwarded-client identity headers before requests enter DSH.
- Patches only the exact `dsh-client-connection` capability probe; every other plugin bundle is passed through byte-for-byte.
- Replaces the host-native directory dialog with DSH's in-app filesystem browser, so remote users can choose a workspace on the DSH host.
- Starts and stops with the DSH plugin lifecycle, so an existing systemd, launchd, or other DSH supervisor also supervises the gateway.
## Architecture

The reverse proxy is the public security boundary. It must provide TLS and authentication. The gateway's source allowlist is defense in depth, not user authentication.
## Install
Requirements:
- DSH web profile
- Node.js 22.19 or newer
- A reverse proxy that supports WebSocket forwarding and authentication
Install from GitHub:
```sh
dsh plugin --profile web add github:Jiachi5533/dsh-remote-gateway
```
Then edit the generated `remote-gateway` entry in `~/.dsh/profiles/web/cordis.patch.yml`:
```yaml
- id: remote-gateway
name: '@jiachi/dsh-remote-gateway'
config:
host: 0.0.0.0
port: 3080
allowedSources:
- 192.0.2.10 # exact LAN address of the reverse proxy
- 127.0.0.1 # optional local health checks
```
Restart the DSH web process after the first installation. Configure the reverse proxy to send HTTP and WebSocket traffic to `http://DSH_HOST_LAN_IP:3080`, and enable HTTPS plus authentication at that proxy.
> Never expose port 3080 directly to the internet. Do not add broad network ranges to `allowedSources`; the current implementation accepts exact IP addresses only.
## Configuration
| Option | Default | Description |
| --- | --- | --- |
| `host` | `0.0.0.0` | Gateway listen address. |
| `port` | `3080` | Gateway listen port. Use `0` only in tests. |
| `allowedSources` | `['127.0.0.1']` | Exact IPv4 or IPv6 source addresses allowed to connect. |
IPv4-mapped addresses are canonicalized, and IPv6 loopback `::1` is treated as `127.0.0.1`. The upstream host and port are obtained from DSH itself and cannot be redirected through plugin configuration. Startup fails if the internal DSH web server is not bound to `127.0.0.1`.
Allowlisted monitoring systems can check `GET /_dsh-remote-gateway/health`. It returns `200 ok` without contacting the DSH upstream, so it verifies the gateway process and source policy rather than DSH readiness.
## Security model
- TLS and user authentication terminate at your reverse proxy.
- Only the reverse proxy's LAN address should be allowed at the gateway.
- The internal DSH web server stays loopback-only.
- `Authorization`, `Proxy-Authorization`, `Forwarded`, `X-Forwarded-*`, and `X-Real-IP` are removed before proxying upstream.
- A source-IP allowlist does not protect against a compromised reverse proxy or another device that can use the same source address.
- DSH plugins execute with the DSH process permissions. Review every third-party plugin before installation.
See [SECURITY.md](SECURITY.md) for reporting and operational guidance.
## Compatibility
| Component | Status |
| --- | --- |
| DSH `0.1.1-rc.2` | Verified on 2026-08-28 |
| HTTP and SSE | Verified |
| WebSocket upgrade | Verified |
| Settings API | Verified |
| In-app host directory browser | Verified |
| Existing and future standard Host/Client bundles | Passed through unchanged, except the exact connection module path |
The connection compatibility patch is deliberately fail-closed. If a DSH update changes the expected probe, that module returns `502` with an explicit log message instead of silently loading a partly broken remote UI.
Most well-behaved plugins continue to work because their client assets and same-origin requests are transparently forwarded. A plugin may still need its own remote adaptation if it hard-codes `localhost`, opens a native host dialog, or independently rejects non-loopback browser locations.
## Upgrade and removal
Update by re-adding the GitHub source from dsh-market or the CLI, then restart DSH:
```sh
dsh plugin --profile web add github:Jiachi5533/dsh-remote-gateway
```
Remove it with:
```sh
dsh plugin --profile web remove @jiachi/dsh-remote-gateway
```
After removal, delete any remaining `remote-gateway`, `directory-picker-browse`, and `ui-directory-picker-browse` rows that were added by this plugin from the profile patch, then restart DSH.
## Troubleshooting
- `403 forbidden`: the TCP source address is not present in `allowedSources`. Add the reverse proxy's LAN IP, not the remote browser's public IP.
- `502 ... compatibility probe matched 0 times`: the installed DSH client connection module changed. Pin a verified DSH version or open an issue with both version numbers.
- Page loads but WebSocket fails: enable WebSocket upgrade forwarding in the reverse proxy.
- Native folder dialog appears: confirm both browse picker rows in `cordis.patch.yml` are enabled and the native `directory-picker` row is disabled.
- New UI plugin is missing: restart DSH after installing the plugin, then hard-refresh the browser.
## Development
```sh
npm test
```
The test suite covers source filtering, IPv4-mapped addresses, trust-header rewriting, fail-closed module patching, and byte-preserving proxy behavior for unrelated plugin bundles.
## License
[MIT](LICENSE)
Install
dsh plugin --profile web add github:Jiachi5533/dsh-remote-gateway#aaf658ddf26909ba4239cd528b6a8f9849997b67
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install jiachi-dsh-remote-gateway from the hub