DEV Community

hadt
hadt

Posted on

How Agoches connects your phone to a terminal on your dev machine: iroh, hole-punching, and our threat model

If you run AI coding agents like Claude Code or Codex for anything longer than a few minutes, you know the feeling: you kick off a task, walk away, and have no idea whether the agent finished, got stuck, or is about to do something destructive. Agoches is a phone app that gives you a live terminal, per-file diffs, and a task board for those agents.

The interesting part isn't the UI. It's this: how do you connect a phone on a random cellular network to a terminal running on a laptop behind a home router — without port forwarding, without a VPN, and without shipping your source code through someone else's server?

This post walks through the answer.

Why not the "obvious" options

SSH + port forwarding. Works, but you have to open a port on your router and give your phone a stable way in. Most people can't or won't, and it breaks the moment you move from Wi-Fi to cellular.

Tailscale / WireGuard. Genuinely great. But it's a separate product to install and manage, and it's global to the device — it sets up a network interface every app shares. Agoches wants connectivity embedded inside the app, so the person using it never has to think about a VPN or a daemon they configure by hand.

A cloud relay that terminates the session. Easiest to build, worst for trust: a server in the middle would see your terminal I/O and your code. For a tool that has shell access to your dev box, that's the one thing we refused to build.

The approach: iroh

Agoches builds its connection layer on iroh, a Rust library for direct, authenticated, end-to-end-encrypted connections between endpoints identified by a public key rather than an IP address.

The core idea: every endpoint has an Endpoint ID — the public half of an Ed25519 keypair. You don't dial "an IP," you dial "an endpoint," and iroh figures out the best available path to reach it. That indirection is what lets the connection survive network changes: relay URLs and direct addresses go stale fast in P2P land, but the Endpoint ID is a stable anchor.

The flow, conceptually:

  1. Identity. The daemon on your machine holds an Ed25519 keypair. Its Endpoint ID is the address your phone dials. (To keep the same ID across restarts, the daemon persists its secret key.)
  2. Pairing. The QR code you scan carries the daemon's Endpoint ID. That's how the phone learns who to connect to — cryptographically, not just where.
  3. Address lookup. Given an Endpoint ID, iroh resolves current dialing details (direct addresses + home relay) through its address-lookup services.
  4. Path establishment. iroh attempts a direct UDP path between the two devices via hole-punching.
  5. Transport. Application data rides over QUIC (TLS 1.3), giving multiplexed streams and a connection that isn't pinned to a single IP:port.

An iroh connection is, in fact, just a QUIC connection — if you looked at it in Wireshark it would be indistinguishable from any other QUIC traffic. No bespoke crypto, no reinvented wheel.

NAT hole-punching, briefly

Most devices sit behind NAT and don't have a public IP. Two peers behind NAT can't just fire packets at each other — routers drop unsolicited inbound traffic.

Hole-punching solves this with coordination. Each peer learns its own public-facing address as seen from outside, the peers exchange candidate addresses, then send packets at each other at roughly the same time. The outbound packet from peer A opens a mapping in A's NAT that lets B's packet in, and vice versa. When it works, you end up with a direct path and the coordination point drops out of the data path entirely.

In iroh's own numbers, roughly 9 out of 10 connections go direct. The relay is only a stepping stone for the rest.

Relay fallback — and why it doesn't break the trust model

When a direct path can't be punched through (symmetric NAT, carrier-grade NAT, TCP-only egress), traffic falls back through a relay. The property that matters: the relay routes encrypted packets and stores nothing.

From iroh's QUIC implementation, a relay is "just another UDP socket." Because the session is end-to-end encrypted between the two Endpoint IDs, the relay sees ciphertext, not your terminal output, not your diffs, not your keys. What it can observe is limited metadata — that Endpoint ID X is talking to Endpoint ID Y and roughly how many bytes — and only until a direct connection is established. iroh's relays don't record even that.

This is the whole point for a tool like Agoches: even in the worst-case network where direct connectivity is impossible, no server ever holds a decryptable copy of your session. The relay is a bandwidth cost, not a confidentiality cost. Relays are also stateless, so they're cheap to run and easy to self-host if you don't want to depend on the default public ones.

A nice side effect of dialing by key over QUIC: when your phone jumps from Wi-Fi to LTE and its IP changes, the session is keyed to the Endpoint ID, not the address — so the link re-establishes its path without tearing down your terminal session.

Threat model

Being explicit here, because the self-hosting crowd will (correctly) ask.

What this design protects against:

  • Passive eavesdroppers, including the relay. All session data is end-to-end encrypted (QUIC / TLS 1.3) between the two Endpoint IDs. On-path observers see ciphertext.
  • Connecting to the wrong peer. Pairing binds your phone to a specific Endpoint ID (an Ed25519 public key). An attacker can't impersonate your daemon without its secret key — this holds as long as the Endpoint ID was exchanged securely, which is exactly what the QR-code pairing is for.
  • Inbound attack surface. No listening port is opened on your router and no publicly reachable service exists, so there's nothing to scan or brute-force from the outside.
  • Cloud data breach of your code. There's no cloud store of your code or sessions to breach.

What it does NOT protect against (and what you own):

  • A compromised phone or dev machine. If either endpoint is owned by an attacker, E2E encryption is irrelevant — they're inside the trust boundary. Use the app's biometric/PIN lock, and treat the daemon as software with shell-level reach.
  • A leaked pairing credential. The QR code is a bearer credential during pairing; treat it as sensitive. (Verify token lifetime/single-use in your own build before making promises here.)
  • Malicious agent actions. Agoches shows you what an agent is doing and lets you approve diffs, but the value of that depends on you reviewing. It's a visibility-and-control surface, not a sandbox.
  • Traffic-analysis-level metadata while relayed. The relay can't read your data, but it briefly sees that two endpoints communicate and roughly how much, until a direct path forms.
  • Post-quantum adversaries. iroh uses Ed25519 and X25519/P-256 today — not post-quantum-secure. Worth stating honestly.

The honest bottom line: the daemon runs with your user's privileges and can drive a terminal. The cryptography makes the transport trustworthy; it does not make the daemon trustworthy. The strongest way to earn that trust is open-sourcing the daemon — which is the direction I'd point anyone evaluating this seriously.

Takeaways

  • Addressing peers by public key instead of IP buys you both mobility (connection migration) and authentication (you connect to the endpoint, not a spoofable address).
  • Hole-punch first, relay as fallback, and keep the relay outside the encryption boundary — that's how you get "no port forwarding" without turning the relay into a privacy hole.
  • For a tool with terminal access, be loud about your threat model. The readers you want are exactly the ones who'll interrogate it.

Agoches is a remote terminal for monitoring AI coding agents from your phone. If you run agents overnight, I'd love your feedback — especially on the trust question above.

Try it yourself at agoches.app.

Top comments (0)