Security

What listens, what connects, and how to check.

Reins connects your phone to your own machine. This page lists every listener, every outbound connection and every server involved, so you can compare it with what you see on your own network. The short version is on the home page.

Trust boundaries

  • Your machine. The reins daemon runs as your user, as a launchd agent on macOS or a systemd user unit on Linux and WSL2. Agent hooks call a small shim, which asks the daemon.
  • Your phone. Holds the pairing and its SSH key. A biometric lock (Face ID, fingerprint, passcode) is available and off by default.
  • The path between them. Your LAN, or Tailscale or a VPN if you use one. That provider is in your network path.
  • Servers. None of them sees your work: our push relay that wakes a sleeping Android phone (alerts are on by default and can be turned off), and RevenueCat, which checks your Pro status with Google Play. Details below.
Where your session travels Two diagrams. Relay-based tools: your phone connects to a relay server, which connects to your machine, so the session passes through the server, encrypted. Reins: your phone connects to your machine directly, and the middle, where a relay would sit, is empty. Relay-based tools phone → relay → your machine phone relay a server machine encrypted so the server can’t read it Reins phone → your machine phone machine nothing of ours in between

What the daemon listens on

127.0.0.1:7777
Loopback listener for the local hook shims. Not reachable from the network.
~/.reins/hook.sock
Unix socket the shims use to reach the daemon. Only your user can open it.
:7778
The transport your paired phone dials. Change it with --transport-listen.
:7779
Pairing service: a phone on the same network or tailnet can pair without a QR, by confirming a code on this machine or with a passphrase you turn on. Other addresses are closed unread. --pair-listen "" turns it off.
_reins._tcp (mDNS)
A LAN announcement so the pairing screen can find the machine. --advertise=false turns it off.

The token

The daemon keeps a random token at ~/.reins/token, mode 0600. It is sent only in an Authorization header, never in a URL. To rotate it, delete the file, restart the daemon and pair again.

Pairing and host-key verification

reins setup (or reins pair on its own) prints a QR. It carries every route to the machine (LAN, Tailscale and so on), your username, the host’s SSH key fingerprints, a pairing token, and an SSH key the host mints and authorises for this pairing. Never a password. Anyone who photographs the QR can reach the machine, so treat it like a key.

The app connects over SSH and checks the host key against the fingerprints in the QR. A mismatch is refused, and the app shows both fingerprints. A QR from an older daemon that carries no fingerprints falls back to trust on first use: the first key seen is pinned, and every later change is refused.

The connection

After the SSH bootstrap, the phone holds a resumable, per-session-encrypted connection to the daemon on the transport port. It survives TCP drops and IP changes, so moving from Wi-Fi to cellular keeps the session. The phone probes every route in the QR and uses whichever answers. A shell nobody is attached to is kept for 12 hours and handed back when you return.

Fail-open

The shim either returns the phone’s decision or returns nothing, and nothing means your agent shows the permission prompt it would have shown anyway. An unanswered approval is never a denial; if you want something blocked, tap Deny.

What the Reins hook does when something fails
If Then
Daemon isn’t running The shim exits immediately. Your agent shows its normal prompt.
Daemon running, no phone attached Exits immediately, unless a push wake is in progress. It doesn’t burn the timeout on a phone that isn’t there.
Phone attached, nobody answers Waits up to three minutes (the hooks pass --timeout 180s), then exits. Normal prompt.
You tap after the shim gave up Ignored. A late tap is never a live approval.
Daemon crashes mid-decision The socket read fails and the shim exits. Normal prompt.
The push relay is down Push is skipped; the approval passes through to the desk prompt as usual.

A paired phone is a shell on that host

A paired phone can type into the terminal your agent runs in; that is how replying works. So a paired phone is as privileged as a shell on that host, and pairing one is a decision of the same weight as adding an SSH key. Pair only your own devices. If you want supervision without that capability, start the daemon with reins serve --allow-chat-send=false: approvals and the live feed keep working, and the phone loses the ability to type.

Revoking a phone

  • Revoke in the app’s Machine tab clears the pairing and token from that phone.
  • The host keeps the phone’s key until you delete its line from ~/.ssh/authorized_keys. Deleting it stops that phone reconnecting.
  • To rotate the token, run rm ~/.reins/token, restart the daemon, and pair again.

Check it yourself

The source is not public, so here is what to run instead. We describe what you should see rather than quote output; compare it with yours.

  1. lsof -nP -iTCP -sTCP:LISTEN | grep reins

    The loopback listener for the local shims (127.0.0.1:7777) and the transport port your paired phone dials (:7778 unless you changed --transport-listen), and the pairing port (:7779 unless you changed --pair-listen). Anything else is worth an email.

  2. lsof -nP -i | grep reins

    Inbound connections from your phone. Outbound only to the push relay while it wakes a phone, and to any webhook URL you configured yourself.

  3. sudo tcpdump -i any -n 'not port 22 and not host 127.0.0.1'

    Run it while you use the app with other apps quiet. Traffic tied to Reins should be the transport flow to your phone, mDNS on your LAN, and the push relay only while a phone is being woken. Compare that with what you see.

What reaches a server

The daemon has no telemetry, no analytics, no crash reporting and no phone-home. It contacts the push relay only while waking a phone (or Google’s Firebase Cloud Messaging directly, if you give it a Firebase project of your own), and any webhook URL you configure yourself (HMAC-SHA256 signed, https only except loopback).

Wake-up push
Optional, Android today. The notification says only “An agent needs you”. The relay stores nothing; the approval itself still travels over your paired connection.
Reins Pro purchases
Google Play and RevenueCat see an anonymous ID and your purchase. Nothing about your machines or sessions.
Dictation
Your phone’s own speech service, so Apple or Google transcribe it. Type if that matters.

More on the privacy page.

Report a problem

Found something that contradicts this page? Email hello@enraio.com. Then read how setup works or see Free and Pro.