How it works

Plain SSH. A key that stays on your phone.

Legio is an SSH client. The free legio command prepares your machine and pairs your phone with it. After that, every terminal, file and metric goes straight from your iPhone to your machine.

The big picture

Two parts. One connection.

The app on your iPhone and the legio command on your machine. They talk over the SSH server that your machine already runs.

Everything you do

Terminals, panes, Docker, files, ports, metrics

iPhone Legio app

Holds its own ed25519 key in the Keychain. Opens one SSH link for each machine.

Your machine sshd, the server you already run

The paired key can open a terminal and reach a short list of local ports. Nothing more.

  • PTY → herdr terminal attach or a shell
  • 127.0.0.1:4499 → Herdr socket (the bridge)
  • Ports you allow with --forward-port

Notifications only

When an agent needs input or finishes

Your machine legio watch

Reads Herdr every 2 seconds. Sends a push when an agent blocks or finishes.

Legio relay Holds the APNs key

Applies your notification rules, then passes the push to Apple.

iPhone “Claude needs input”

Tap it to open that machine in the app.

From zero

Three steps to your first pane.

1

Install legio

One binary for macOS and Linux. The installer checks it against the release's SHA256SUMS before it puts it in ~/.local/bin.

$ curl -fsSL https://legioapp.cloud/install | sh
2

Run it and pair

It sets up the Herdr bridge and the notification watcher, checks your SSH server, and shows a QR code. Scan it with the app.

$ legio
3

Tap a machine

The app logs in with its key, lists your Herdr workspaces or tmux sessions, and attaches to the real terminal of a pane — live, with no polling.

Pairing

Your phone makes the key. You approve it.

The QR code holds no password and no key. Only the public half of a key ever leaves your phone, and it gets in only when you say yes at the terminal.

  1. MachineShows a QR code

    It holds the address, the SSH user, the ports, and a one-time token: 32 random bytes that work for one pairing, for 10 minutes.

  2. PhoneMakes an ed25519 key pair

    The private key goes into the iOS Keychain and stays there.

  3. PhoneSends the public key, signed with the token

    An HMAC-SHA256 made with the token proves the request came from someone who scanned the code. The machine checks it in constant time.

  4. YouCompare five words

    The terminal and the phone both turn the key into a phrase like fee-jazz-naive-fruit-equip. If the words match, type y.

  5. MachineAdds one restricted line to authorized_keys

    Then it closes the pairing port. The token is spent, whatever the answer was.

  6. PhoneSaves the connection and logs in

    From now on it is plain SSH with its own key.

you@vps-01
$ legio
✓ Bridge 127.0.0.1:4499 → herdr.sock
✓ Notification watcher is running
✓ sshd accepts key login
Scan this code with the Legio app.
It works for 10 minutes, and you confirm
that phone here before it is let in.

A phone sent its key: iPhone
The app shows five words. Check that they match these:
fee   jazz   naive   fruit   equip
  Add it to authorized_keys? [y/N] y
✓ Paired. Sent a test notification. 
On your machine

Everything legio changes. Nothing else.

The services run as your user, and only the pairing port listens beyond the machine — until the pairing ends. You can run legio again at any time: a step that is done is checked, not done again.

The bridge

herdr-bridge.socket · com.legio.bridge

Herdr listens on a Unix socket. The bridge makes that socket a TCP port on 127.0.0.1:4499 only, so the app reaches it through the SSH link and never over the network. On Linux it uses systemd-socket-proxyd, which ships with systemd. On a Mac it is a LaunchAgent with socat.

The notification watcher

legio-watch.service · com.legio.watch

A user service that runs legio watch. iOS stops an app's SSH connection soon after it goes to the background, so the machine tells the phone when an agent changes to blocked or done.

One line for each phone

~/.ssh/authorized_keys

Each paired phone gets one line, with a comment that names it. legio keeps the lines that are not its own byte for byte.

restrict,pty,port-forwarding,permitopen="127.0.0.1:4499" ssh-ed25519 AAAA… legio-app:iPhone

The push list

~/.config/legio/push.json · mode 600

One device secret for each phone that gets notifications. Only you can read the file. It holds no Apple token and no APNs key.

What it never touches

/etc/ssh/sshd_config

The system SSH configuration. legio checks for key login and tells you how to turn off password login, but you make that change yourself.

Security

Safe by design, not by promise.

A phone is easy to lose. So Legio gives each phone the smallest access that works, and makes every step of the pairing one you can see.

The private key never leaves the phone

The phone makes its own ed25519 key and keeps it in the iOS Keychain, which encrypts it at rest. The machine gets only the public half. Passwords for connections that you add by hand go in the Keychain too.

A QR code that opens nothing

The code holds a one-time token, not a login. The token works for one pairing and for 10 minutes. After 5 requests with a bad signature, the pairing port closes.

HMAC-SHA256 · constant-time check · 16 KB body limit

You approve every phone

No key goes in until a person at the terminal types y. You compare five words, not a long fingerprint. A key made to match those words takes about 3.6 × 1016 tries, inside the 10 minutes the code lives.

55 bits · BIP-39 word list

A key that can do only what the app needs

OpenSSH's restrict turns everything off. Legio turns back on a terminal and forwards to the bridge port. A stolen phone key gets no agent forwarding, no X11 and no tunnel to other hosts.

The Ports tab reaches more only when you allow it with legio options --forward-port 3000.

Loopback only, on both ends

The bridge binds 127.0.0.1 on the machine, so only an SSH user can reach Herdr. A port forward binds 127.0.0.1 on the phone, so a dev server never shows up on the café Wi-Fi.

Push without the keys to Apple

Only the relay holds the APNs key. Your machine holds one device secret for each phone, and never sees the phone's Apple token. The relay stores the SHA-256 of each secret, not the secret, and sends 30 pushes a minute at most for one phone.

A sealed push secret

The pairing request is plain HTTP, so the phone seals its push secret with ChaCha20-Poly1305 and a key made from the token. The seal is bound to one connection. Later, the app sends the secret again over the authenticated SSH link.

Careful with authorized_keys

Before each change, legio makes a backup copy. It writes the new file beside the old one and renames it into place, so a full disk or a crash cannot leave half a file. It never edits a line that is not its own.

Verified updates

The installer and legio update check each download against the release's SHA256SUMS. The update runs the new binary once before it replaces the old one. If a step fails, the old binary stays.

legio update --version v0.1.0 goes back
WhatWhere it goesWho can read it
Terminals, panes, Docker, files, ports, metrics iPhone ⇄ your machine, over SSH You and your machine
Your private key and passwords iOS Keychain only The app on your phone
A notification Machine → relay → Apple → iPhone The relay and Apple see its text: the agent's name, its status, and the workspace and pane title
Your notification rules The relay The relay, to drop the pushes you do not want
An account There is none Apple keeps your purchase with your Apple ID
Stay in control

Lost a phone? One command.

Every phone has a name and a key phrase. Remove one, and its key cannot log in again.

See and remove phones

List the paired phones with their key phrases and what each one may do. Remove one by its name or its phrase.

$ legio devices
$ legio unpair iPhone
$ legio push remove iPhone

Check, or remove it all

check reports on the bridge, Herdr, the watcher, sshd and the phones, and changes nothing. uninstall removes the services, the push list and every phone key.

$ legio check
$ legio uninstall

Tip: put the machine on Tailscale so port 22 never faces the internet. legio finds the Tailscale address and puts it in the QR code for you.

Legio app icon

Read the code. Then pair.

The legio command and its pairing protocol are open on GitHub.