-
-
Notifications
You must be signed in to change notification settings - Fork 11
Relay
Relay mode reaches your harness through
dsh-relay — a plugin that runs beside the
harness and asks who you are before letting anything through. It is the answer to the problem
LAN Mode does not solve: that page serves a coding agent to everyone on your network.
Requires DSH Mobile 0.8.0 or newer and dsh-relay 0.1.1 or newer.
The harness is explicit that it has no authentication layer, and refuses dsh web --host 0.0.0.0
because doing so "would expose remote code execution to the network". LAN mode works around that
refusal with a config patch. The relay is the layer itself: the harness keeps its loopback bind,
and a second listener in front of it terminates TLS, checks a credential, and forwards.
That ordering matters. A relay that fails to start leaves the harness unreachable from the
network — never open to it. And it refuses to start at all if it finds the harness already bound to
0.0.0.0, so you cannot accidentally run both.
-
A real credential. This device holds a bearer token issued once, at pairing, and sends it on
every
/apicall and both event streams. The relay stores only a keyed hash of it. - Encryption, with the key pinned. The app pins the relay's public key rather than trusting a certificate authority, so a self-signed relay is verified rather than merely accepted.
-
Revocation you control. Every paired device is listed at
/relay/deviceswith its own revoke button, and "sign out everywhere" invalidates every token at once. - It works from outside your Wi-Fi. Forward the relay's port and it keeps working; the app's "not on this phone's network" check is skipped for an endpoint that holds a token.
What it does not do is reduce what an authenticated client can reach. Pairing a phone gives it the same power as a terminal on that computer, because the agent runs commands there. The question the relay answers is "is the remote user you", not "how much can the remote user do".
On the computer running the harness:
dsh plugin --profile web add dsh-relay
dsh webThe terminal prints the relay's addresses. Open one on that machine and set a password — until one exists the sign-in page is loopback-only, so nobody on the network can claim the relay first.
Then open /relay/pair on that machine. It shows a QR code and an eight-digit code; either works,
and both expire after a few minutes.
In the app: Relay → Pair a relay.
- Scan QR code — point the camera at the code on the pairing page. This is the better route, and not only for convenience: see below.
- Or type the code — enter the relay's address and the eight digits yourself. Useful when the camera is unavailable or the QR will not focus.
Name the device something you will recognise in the relay's device list, and tap Pair. The app stores the token and connects.
The app says which one happened, because the difference is real:
| What it establishes | |
|---|---|
| Scanned QR | The payload carries the relay's public key, so the very first byte the app sends is checked against it. |
| Typed address | Carries no key — the relay only reveals one in its answer — so the certificate is trusted on first contact. |
Trust on first contact means an attacker able to answer at that address during pairing would end up holding the enrolment. Over your own Wi-Fi that is a narrow window; over an untrusted network it is not. Scan the QR when you can.
If the relay is running tls: off, the app says so plainly: the token and everything you send
travel in the clear.
Find relays browses for the relay's mDNS advertisement (_dsh._tcp), which carries the port,
whether the listener speaks TLS, and the key to pin — so it answers in a second or two rather than
sweeping a subnet.
Nothing depends on it arriving. Plenty of networks filter multicast and the relay's mdns flag can
be turned off, so a quiet browse falls back to knocking the two ports a relay actually uses. It also
knocks 127.0.0.1 first, which is where a relay reached through adb reverse answers.
A relay that answers but refuses the address you reached it by is listed anyway, with the entry to add to its configuration. That is the most recoverable thing a scan can find — see below.
The relay answers HTTP 403 for every unauthorised case and never 401, so the app works out which one it is from what it already knows about the address.
"The relay refused this device." Your token expired, or it was revoked from the relay's device list. Pair again. The app stops retrying rather than backing off forever — there is nothing to wait for.
"Refuses this address" / "…it refuses requests addressed this way." The relay only answers to
authorities it knows: loopback, its own addresses, and whatever its operator listed. An emulator's
10.0.2.2, or a hostname it was never told about, gets refused before the pairing code is even
read. Add the address to publicHostnames in the relay's cordis.patch.yml and restart it — or
reach the relay by an address it already knows. In the harness log this looks like:
[dsh-relay] refused GET /relay/health from 127.0.0.1: untrusted-host
"This relay's key changed." The relay mints a new key whenever the set of addresses its certificate covers changes, so a laptop that moved networks produces this — and so would something else answering at that address. The app will not guess between them. Pair again only if you were expecting it.
dsh-relay ships with compat.addressGrants on, which accepts requests from whatever address a
paired device was last seen on. It exists for DSH Mobile 0.5.0, which had no field that could carry
a token.
A source address is not authentication — it is shared behind NAT, reassigned by DHCP, rotated by
IPv6 privacy extensions, and spoofable by anything on the same Wi-Fi. Once every client you use is
on 0.8.0 or newer and has paired, set compat.addressGrants: false. Nothing in this app needs it.
- Security — the trust model for both modes
- LAN Mode — the unauthenticated alternative, and why to prefer this
- Connecting — the connect screen itself
-
docs/PROTOCOL.md— the wire contract, for developers
DSH Mobile — an unofficial, community-built Android remote for the
DeepSeek Harness. The harness and its brand belong
to their respective owners.
MIT ·
repository ·
canonical technical docs in
docs/.