Open source, secure, auditable

Every layer is public, every remote connection has to complete a cryptographic handshake, and every security finding is published and fixed.

DSH does not treat open source as a slogan. macOS, Android, the Chrome extension, the relay and computer use each live in their own repository — readable, self-hostable, buildable. The only public component of mobile access is the relay, and it holds none of your data. The review below documents eight findings, every verified reproduction, and the measured result after each fix.

Open source

One repository per component — read it, self-host it, build it yourself, with the build scripts included.

  • Five repositories published under the MIT license
  • The relay dsh-proxy is a single-port Rust implementation: no TLS, no certificate, no config file, no disk state
  • Point clients at your own relay instead of our hosting

Secure

Remote access opens no port, writes no state file and leaves nothing to restore. Pairing credentials are single-use.

  • Noise_IK end-to-end encryption: a device gets not one byte before the handshake completes
  • No listening port, no state file, no extra daemon process
  • One pairing code per device, expiring after 5 minutes
  • Chrome control reuses your signed-in browser and never uploads cookies

Auditable

We ran a full adversarial security review of our own remote access path. All eight findings are fixed and published.

  • The review carries reproducible attack scripts and live measurements, not a paper checklist
  • Every finding records how it was fixed and the post-fix measurement
  • Every tool call is logged — command, diff and output stay reviewable
  • Permission tiers (plan / read-only / workspace write / full access), switchable at any time

Security review: eight findings, all fixed

An adversarial review of dsh-mobile-bridge + dsh-proxy. The threat model is an internet attacker reaching this machine's /api through the public relay.

  1. V1

    A paired phone could mint new pairing QR codes

    High Fixed

    mobileBridge/* now returns 403 to phones and stays reachable only over the Mac app's authenticated local channel. Measured: 403 from a phone, 200 from localhost.

  2. V2

    A paired phone could enumerate status and revoke any device

    High Fixed

    Moved out of phone reach together with V1.

  3. V3

    A reached device got the entire /api, including file read/write and agent RPC

    Critical Narrowed

    The mobile channel now uses a default-deny exact-route allowlist (/api/file, /api/session/uploadFileBinary, /api/remote.mux, extensible by config); unlisted plugin routes return 404.

  4. V4

    Revoking a device did not terminate its live sessions

    High Fixed

    The registry keeps a dispose handle per live connection, called by revoke and TTL expiry. Measured: a revoked device's session dropped in about 0 ms, previously 30 s or more.

  5. V5

    Relay: unbounded unauthenticated bridge registrations

    High Fixed

    Admission control before the handshake: a global permit pool (--max-bridges, 1024) plus a per-IP RAII guard (--max-bridges-per-ip, 32). Measured: 3000 rogue bridges from one address — 31 admitted, RSS delta 0 MB, previously 3000 admitted at +180 MB.

  6. V6

    Relay: unauthenticated attacker could exhaust one bridge's stream budget and lock out the victim

    High Fixed

    The preamble and the Noise_IK handshake share one 10 s deadline, and the relay's stream slot is an atomic semaphore reservation rather than check-then-insert. Measured: 2100 silent streams all reaped, a real phone served 10 ms later, previously denied indefinitely.

  7. V7

    Relay: no timeout around the XX handshake (slowloris)

    Medium Fixed

    The whole XX handshake is wrapped in --handshake-timeout-ms (10 s), and the handshake frame length is validated against a 1 KiB ceiling before allocation instead of reserving a 64 KiB buffer per connection.

  8. V8

    Relay inbox bounded by frame count, and the stream-cap check was TOCTOU

    Low-Medium Fixed

    A stream that overruns its inbox loses only that stream (the relay sends KIND_CLOSE and reclaims its credit); the stream cap is now an atomic permit held for the stream's lifetime.

Summary of the review's conclusions. The review also recorded what it could not break: the Mac was unreachable without a valid device key or a live pairing token, and no pre-auth RCE or fabricated-device path was found.

What the review could not break

  • No relay-side routing-key hijack: the routing key comes from the handshake static key, so a third party cannot register a bridgeKey it does not hold.
  • The per-device gate holds: unpaired devices are refused, the pairing token is single-use, and a wrong magic is refused.
  • Frame sizes are bounded: no length-prefix-driven unbounded allocation.
  • No reachable panic in the relay's parse paths under panic=abort.

Known residual risk (published as-is)

  • A stale bridge keeps V6 open: the relay's permit stops it over-issuing slots, but releasing them depends on the bridge's timeout, so relay and clients must be upgraded together.
  • The RPC channel is still the whole RPC surface: the allowlist removes plugin routes a phone has no business calling, but a paired device is still a full harness client by design — V3 is narrowed, not eliminated. Revoking a lost phone is the intended remedy, and it now takes effect immediately.
  • Per-IP bridge caps dislike shared egress: behind an L4 load balancer or a large NAT every bridge presents the same address, so --max-bridges-per-ip must then be raised deliberately.

GitHub repositories

Every source location, by component.

The source is all here — go read it

The full review, the reproducible attack scripts and the measurements live in the repositories.