What can see what.
Togo connects your phone to computers you own, and your terminals stay on them. Your agents still send prompts and code to their model providers, as they do at your desk. This is the threat model: what each part can read, how it's protected, and where that protection stops.
Who can do what
| Who | Can | Can't |
|---|---|---|
| Someone on the network | See and change traffic between your phone, your machines, the relay and Apple | Read or forge SSH, Mosh or sealed notification traffic |
| Whoever runs the relay | See sealed envelopes, when they're sent, their sizes, your host id and your devices' push tokens; read Live Activity status fields | Open a notification or an answer, or forge an answer the helper accepts |
| Someone holding your locked phone | Read notifications on the lock screen, if your iOS previews allow it, and deny a waiting request | Approve without unlocking (the default), open a terminal or use your SSH key |
| Someone with a stolen approval key | Answer a request that is waiting right now | Type anything, choose “always allow” or open a shell |
| A repository you open | Carry git hooks, filters and odd file names | Run them through Togo's git views |
| A tampered release mirror | Serve any file at a release address | Get the install script or togo update to install it |
Getting into your machine
The helper opens no network port. The app reaches it by running togo serve --stdio over SSH, so SSH's own login and host key check cover every request. Agent hooks and the desk commands talk to the helper through a unix socket that only your user can open.
Your SSH key is made on the phone: in the Secure Enclave where it can be, otherwise in the Keychain. Either way it stays on that device, it is never synced, and every use takes Face ID, Touch ID or your passcode.
You add the app's public key to ~/.ssh/authorized_keys yourself. The first time you connect, the app shows the machine's host key fingerprint in the SHA256: form ssh-keygen -l prints, and remembers it once you confirm. If the key later changes, the connection stops. When the app tries several addresses for one machine, a changed key on any of them stops them all.
Notifications
When you pair a phone, it and the helper exchange keys over the SSH connection you already trust, never through the relay. Each notification is then sealed on your machine for one device: a fresh X25519 key agreement for every message, a key derived with HKDF-SHA256, and ChaCha20-Poly1305 to encrypt and authenticate it. The relay forwards bytes it can't open and doesn't log them. Your phone opens them.
Live Activities
iOS draws Live Activities and the Dynamic Island straight from the push, without the app, so a few status fields can't be sealed: the session's number, the agent's name, its state, how many requests are waiting, when it started, the waiting request's id and, when a session ends, one word for why. The relay and Apple can read those.
Everything else, including the session's title, what it's doing, the question and the command, travels sealed for the one device it goes to. Command text never goes in a plain field.
While a Live Activity is running on a phone, its update stands in for that phone's notification for the same event. The update lights the screen and plays the alert sound, with alert text the relay writes from the plain status alone, and the lock-screen card offers the same Approve and Deny as the notification. A phone with no running activity, and an Apple Watch, gets the regular notification. When an activity has just started and hasn't registered within 10 seconds, the notification is sent anyway.
Answers from the lock screen and Watch
An answer from a notification, a Live Activity or the Watch can't wait for Face ID, so it doesn't use your SSH key. Each paired phone and Watch holds a separate Ed25519 approval key that can do one thing: approve or deny one named request. The answer is sealed to your machine and travels through the relay.
The helper accepts it only when all of these hold:
- the signature matches a device you paired and haven't removed;
- the answer is less than ten minutes old and hasn't been seen before;
- the request it names is still waiting;
- the same prompt is still on screen when the helper looks again, just before it types.
A one-tap answer never picks “always allow”, and typed replies are refused on this path. Both stay in the app, over SSH. Approving on the lock screen, from a notification or a Live Activity, needs the phone unlocked first, unless you turn on Approve without unlocking in the app's notification settings. On the Watch, approving always needs the Watch unlocked. Deny never needs the phone unlocked.
The relay
The relay is a small Cloudflare Worker that hands notifications to Apple and holds answers for your helper. It sees when pushes and answers happen, how big they are, your host id, your devices' push tokens and the Live Activity status fields above. It can't read anything sealed.
Answers wait in a mailbox your helper reads over a connection it opens itself. Each one is deleted when the helper confirms it, or after ten minutes. A helper can also leave up to eight notifications sealed in advance, sent if it goes quiet. The relay can send them but not read them, and drops them after 24 hours.
If the relay is down, notifications and lock-screen answers stop, and the app keeps working over SSH.
Repositories you open
Every git command the helper runs has the repository's hooks and filter drivers switched off for that command, and commits made from the app skip commit hooks and signing. Session names, folders and agent names reach tmux and git as separate arguments, never through a shell.
Your agents' secrets
The helper never reads the credential files your agents keep. It reads each agent's hooks, status line, transcripts and session files through that agent's adapter, and strips secrets from all of it before anything leaves your machine. An API key you enter in the app for an agent's provider goes over SSH into that agent's own store. Togo doesn't keep it.
Releases
Each release's SHA256SUMS file is signed with minisign, and the signature names the version, so an older signed file can't pass for a newer release. The install script and togo update carry the public key, check the signature and the download's checksum, and write nothing until both pass. The install script checks the signature with minisign, or with OpenSSL 3 when minisign is missing; on a Mac with neither, it instead requires the program to pass a code-signature check against Togo's Developer ID. On macOS the program is also signed with a Developer ID and notarized.
Known limits
- Live Activity status fields are not encrypted, as described above.
- With iOS previews set to Always, opened notifications show prompt text on the lock screen. Set previews to When Unlocked to hide it.
- Every phone you add holds a key with full shell access as your user, and Togo doesn't limit what that key can run. A stolen phone with its passcode can do anything you can. Remove the device with
togo devices revoke, which also tries to take its key out ofauthorized_keysand says if it couldn't. - Mosh's server listens on a UDP port between 60000 and 61000 while a session is open. That is how Mosh works; the helper itself still listens on nothing.
- Eternal Terminal's server, if you run it, listens on TCP port 2022. Togo uses it only for a machine where you pick it, because its client cryptography hasn't been audited.
- An agent that serves an API, such as OpenCode, listens on loopback or a unix socket while its session runs. Other programs running as you can reach it, as they could reach the agent anyway.
- The helper runs as you and doesn't sandbox agents. Anything running as your user can already do what the helper can.
This website
No analytics, no cookies, and nothing loaded from other servers. Every page is a static file.
Reporting a vulnerability
Please report vulnerabilities privately, not in a public issue. Use Report a vulnerability on the repository's Security tab, which only the maintainers can see, or email [email protected]. Say what you found, how to reproduce it and what an attacker gains. You'll hear back within a few days.
The security model in the docs walks through keys, pairing and the relay step by step.