Security model
What is protected, what is not, and why — stated plainly, because a tool that hands out a shell on your machine owes you that.
The boundary is pairing
A paired iPad can open a terminal on your host. That is the product, not a flaw. It also means pairing is the security boundary — everything else is accident-prevention layered on top of it.
How pairing is guarded:
- The QR carries the agent's certificate fingerprint, so the iPad pins the right machine before it sends anything. A man in the middle fails at the handshake.
- The pairing code is single-use and expires after ten minutes.
- The iPad generates a key pair in its Secure Enclave. The private key cannot be extracted, even from an unlocked device.
- Changing a device's key requires a fresh pairing code — knowing a device's identifier is never enough.
- A new pairing shows up in the tray immediately, and can be revoked there or with
spadeide unpair.
The session
- Mutual TLS 1.3. The iPad verifies the agent by exact key match; the agent verifies the iPad against the key recorded at pairing. No certificate authority is trusted by either side.
- No bearer tokens. A token can be replayed if it ever leaks; a key challenge cannot.
- There is no "continue anyway" button on a pin mismatch. That single affordance would undo the whole model, so it does not exist.
The filesystem sandbox — and what it is not
Opening a folder as a project grants write access to it. Everything else is read-only, path traversal is rejected, symlinks that escape are refused, and reads and writes are capped at 32 MiB.
A system install adds Unix login on top of pairing: each tablet signs in once with username and password (over the already-encrypted session; the password is never stored), then the file API is jailed to that home (including rewriting root-owned files inside it). The terminal is still that user, never root — same groups as a normal login. Deleted account / password change invalidates the bind. See user vs system and on the iPad.
Remote access
Off by default. While it is off, the agent makes no outbound connections at all.
With it on, three properties hold, and they are the reason the design looks the way it does:
- The introducer cannot read anything. The iPad and agent run their own mutually-authenticated TLS session inside the relay. It carries ciphertext.
- The introducer cannot grant access. Authorisation is the agent checking the device key. The server is never consulted.
- The introducer stores nothing. In-memory state with a TTL; a restart loses it all and costs one reconnect.
Agents register under a handle derived from the pairing secret that rotates hourly, so the server does not learn a durable identity. It does still see IP addresses, timing and volume — blinding removes the stable name, not the network metadata. Prefer to trust nobody? Run your own.
No inbound port
Remote access exists so you never have to forward a port. The agent dials out.
Forwarding 7422 puts your host's listener in front of the entire internet and
gains you nothing — if you are doing it, turn on remote access and stop.
Credentials
Git runs non-interactively on your host with your own credential helpers and SSH keys. A push that needs a password fails with a clear message instead of hanging, and you finish it in the terminal. SpadeIDE never asks for, stores or transmits a git credential, and AI CLI API keys stay on the host too.
Reporting something
If you find a problem in any of this, please report it privately before publishing, and you will get a straight answer about severity and timeline.