What SSH Terminal knows about you

Plain answers, and where in the code each one is enforced. This is the user-facing version of ADR 0006; if the two ever disagree, the code decides and this document is wrong.

The short version

Your SSH session is encrypted between your browser and your server. The relay moves bytes it cannot read. We never see your passwords, your keys, or anything you type or run.

What never leaves your device

Private keys. Imported keys live in IndexedDB, which is per-device and per-origin. They are deliberately not in the synced settings, so they do not travel to your other machines — you import them again there. The agent signs with them locally and sends a signature, never a key.

Host keys (known_hosts). Written by OpenSSH into the same local filesystem. One accepted host leaves several entries — the server hands over its other host keys once the first is verified — so the manager lists a host once and forgetting it drops every key it was trusted for. Forgetting one and leaving the rest would keep the host trusted.

Passwords and passphrases. Typed into a form that hands them straight to the SSH client running in your browser. They are not stored, not logged, and not sent anywhere but into the encrypted session.

Session output. What a server prints stays in the terminal's scrollback in the page, and goes nowhere else. "Save output" writes that scrollback to a file on your machine, through the browser's own download — nothing is uploaded, and nothing is written unless you ask for it.

What syncs through your Google account

Connection profiles — label, host, port, username — and terminal preferences, via chrome.storage.sync. That is Google's sync, tied to your Chrome profile. It does not reach our servers. Delete the extension's data in Chrome and it is gone.

Importing an ssh_config reads the file in the browser and keeps nothing of it: the hosts it describes become ordinary profiles, and so they sync like any other. The file itself is not stored or uploaded, and the keys it names are not read — a key still has to be imported by hand, and stays on the device.

What the relay sees

MV3 extensions cannot open a TCP connection, so every session goes through a relay we run. It is a byte pipe: SSH is end-to-end encrypted, so what passes through is ciphertext.

Picking a relay is a measurement, not tracking: the extension pings each hosted relay's /healthz and connects to the fastest. Like any web request, a ping shows that host your IP address. The winner is cached on your device (chrome.storage.local) for six hours, so most sessions ping nothing; a custom endpoint is never probed at all.

For each session its journal records, and nothing else:

RecordedWhy
Destination host and portThe only thing that answers an abuse report
A random per-install idCounts concurrent sessions so one client cannot park hundreds
A random per-session idTies one failed session to one entry when you report a problem
Byte count and durationCapacity, and the transfer cap
Time opened and closedThe above

Retained 30 days, then rotated out by the systemd journal (infra/cloud-init.yaml.tftpl). There is no database, no account, and no profile of you: the per-install id is a random UUID that maps to nothing.

What the relay does not record: your username, anything you type, anything the server sends back, or your IP beyond the connection itself.

You can point the extension at your own relay, or run ours — it is in this repository under packages/relay/ — and the journal lines are written in one function you can read.

Error reports

If a release build has a Sentry DSN configured, crashes are reported. The rule is enforced in code, not by policy: telemetry never carries a destination. Reports contain the shape of the error and the session id; the scrubbers in telemetry/scrub.ts strip hosts, users and IP literals from anything on its way out, on both the extension and the relay. A custom relay you point the extension at is reported only as custom-relay, never by name. A local build with no DSN has the SDK removed at build time entirely.

Crash reporting has an off switch — the Privacy section of the manager — and the choice is honoured before the SDK starts, per device. One honest caveat: the session id in a crash report also appears in the relay journal above, so for the journal's 30 days we can join a crash to one session's destination. That join is the feature — it is how a reported failure gets diagnosed — and it expires with the journal.

If you uninstall

Chrome opens a page on sshterm.dev when the extension is removed, and that page asks why. It is optional, and closing the tab is a complete answer. If you do send it, what reaches us is the option you picked and whatever you typed in the box — no name, no address, no identifier tying the answer to your installation, your servers, or anything you did with them. Which means we cannot reply to it, and cannot delete "your" response on request, because nothing marks it as yours. Responses expire after a year.

The site itself carries no analytics and sets no cookies. The uninstall form is the only thing on it that sends anything anywhere, and only when you press the button.

What we do not do

No analytics. No advertising. No selling or sharing data. No AI features reading your terminal. No account required, because there is no account.

Contact

Abuse reports and privacy questions: the address on the relay's own page, which every relay serves at its root.

Last updated 1 September 2026. The canonical text is docs/privacy.md in the repository; this page is generated from it.