Yesterday's post on shell.online accounts covered what the accounts service stores and how a browser-started password is sealed to one machine. This one is about the part that turns terminal sharing into a team feature: a session has an owner and an assignee, the owner can hand it to a colleague while it is running, and everyone in the organization can watch. The interesting part is what has to move between people for that to work, and what never does.
A version note first. The changelog files all of this under 0.9.0, but there is no 0.9.0 tag. v0.10.0, tagged on 2026-09-08, is the first release carrying the web app, and v0.11.0 and v0.11.1 followed within a day. Everything below is read from the current main branch of the shell.online source.
One organization per account
Signing up creates an organization, named from the email domain unless the domain is on a hardcoded list of fifteen personal providers, in which case it becomes "Firstname's organization". Everyone belongs to exactly one.
Invites are links of the form /join/<id>. Each works once and expires after seven days. An invite can be pinned to an email address; left blank, the Team page labels it "Anyone with the link" and tells you to send that one carefully. Owners and admins invite members, and only the owner can invite an admin.
Owner, assignee, watcher
When a linked machine publishes a session, the record gets an owner and an assignee, both set to whoever started it. Everyone in the organization sees it in the list. Owner and assignee can type. Everyone else watches, and the pane says so: "Watching. Only the owner and assignee can type."
Handoff changes the assignee. The session's owner may do it, and so may an organization owner or admin. The new assignee gets an "assigned" notification, counted apart from mentions in the inbox, and the audit log records a handoff event naming them. v0.11.0 fixed a bug where a colleague already watching when handed the session had to reopen it before typing.
What handoff does not change is who controls the machine. Only the session owner can queue a stop, and only the owner can publish key shares. The server puts it precisely: "Assignment grants terminal input, not control over the owner's machine, and an organization role must never silently broaden into remote-process administration." Starting from the browser follows the same line. The New Session dialog lists your own linked machines, and the start route looks the device up in the caller's own device list. You start work on your machines and hand it to colleagues. You do not start processes on theirs.
Two envelopes, two keys, a service that opens neither
The password for a browser-started session travels twice, sealed both times.
The first envelope goes to the machine. The daemon that shell login starts generates an ephemeral P-256 key pair per run and publishes the public half on every two-second poll, so a restart re-keys. The browser generates its own ephemeral P-256 pair, runs ECDH against the machine's key, derives a 256-bit AES-GCM key with HKDF-SHA256 and the info string "shell.online cli password v1", and encrypts the password under a random 12-byte nonce. The accounts service stores the sender key and sealed password as opaque strings. The daemon opens the envelope with the Go standard library's crypto/ecdh and crypto/hkdf and hands the result to the wrapped command as SHELL_ONLINE_E2EE_PASSWORD.
The second envelope goes to colleagues. Each browser generates a longer-lived P-256 pair on first use, keeps it in localStorage, and publishes the public key with its membership on every load. When the owner's browser sees a session it holds the password for, it seals that password once per member who has published a key, with a fresh sender key and a different info string, "shell.online session password v1", and sends the batch to the session's keys route. The server checks that every recipient is a member and stores the shares. Anyone listing sessions gets back only the share sealed to them. The store holds every share and can open none, which is what the security page means by "sealed once per organization member".
Two edge cases are in the code rather than the copy: a private browsing window cannot keep a key pair, so sharing will not reach it, and a member who has never signed in has no published key and is skipped. The Go tests in the repo seal with the same construction the web app uses, so both ends are checked against one envelope format.
The thing the service does record
Terminal output never reaches the accounts service. It stays inside the end-to-end encrypted stream between the machine and the browsers holding the password, and the share URL is filtered before publishing so only a #salt= fragment survives. The password never reaches the service in the clear either. Typed input does. The commit that landed in v0.11.0 is titled "Record what is typed again, and say so in the terms". The terms now say in bold that what you type into a session from the web app is recorded in plaintext and visible to the team, including a password pasted into the wrong window.
Yesterday's post said input events were no longer accepted. As of 0.11.0 that is wrong, and the correction belongs here. The terms also give the two ways out: a read-only share accepts no browser input at all, and anything typed in the terminal the session was started from is never seen by the browser or by us.
Consent, every time
shell login now asks on every interactive login whether the browser may start sessions on this machine, with the previous answer as the default. Without a terminal the answer stands as it was: "a script cannot consent for anyone, and it must not revoke on their behalf either".
That is the posture Pilot Protocol takes between agents. Nodes are private by default and a peer is reachable only after a signed mutual trust handshake, as the Pilot README lists under Security. A browser reaching a machine gets nothing until a person at that machine says yes, and a colleague reaching a session gets keystrokes at most, never the machine.
Top comments (0)