Privacy and Security
Documentation in progress
Some sections are still incomplete.
ThinkTerm collects nothing about you, your machines or how you use it. There is no telemetry, no analytics and no tracker, and the project has no server for any of that to reach. Apart from one update check, everything ThinkTerm sends over a network is something you started.
What leaves the machine on its own
Only the update check. While the desktop app is running it asks GitHub's release API whether a newer release exists: about ten seconds after a launch, if the last check on record is older than the frequency, and from then on whenever that much time has passed again. The frequency is Check Frequency in Settings › Software Update — Every 12 hours, Daily or Weekly — or, until you have used that row, check_for_updates_interval_seconds, which is twelve hours unless you change it. The command-line tools, the TUI and the session server never check on their own. Check for Updates in Settings › Software Update makes the same request when you press it.
The request is anonymous. It carries no credentials unless you set THINKTERM_UPDATE_TOKEN yourself, which is only useful for reaching a private fork. ThinkTerm's own code never reads GITHUB_TOKEN, so a token you exported for other tools is not sent by a check you did not run. The install script does read it, and an update runs that script; see below. GitHub sees the request as it sees any other; the people who make ThinkTerm do not.
The Check Automatically switch in Settings › Software Update turns it off, and so does check_for_updates = false in the config until you have used that switch. A change applies within a minute. Configuration describes the two, and Updates the rest of the update flow.
What leaves the machine when you use it
| What you do | Where it goes |
|---|---|
| Connect over SSH, Mosh or ThinkTerm Connect | The host you connect to |
Connect to a host with Detect OS or Detect remote programs on — both are, for a new host | That host, over the same connection: a check of its distribution; or ThinkTerm's shell integration copied into ~/.config/thinkterm/ there, with a line added to the login shell's startup files to load it, after which the shell reports each command line to ThinkTerm as it starts |
| Open a Note that shows an image from a web address | The server that address names |
Press Check for Updates in Settings, or run thinkterm update --check | GitHub's release API, with the same anonymous request as the background check |
| Connect to a ThinkTerm Connect host that has no ThinkTerm, and agree to install it | GitHub, from this machine and only after you agree, to look up the release and fetch a signed install script, which is sent to the host over the SSH connection; then the host downloads the release from GitHub, as the SSH user, under ~/.local |
| Agree to update a remote server | The same as for an install, once you answer yes to the one question, which says what will change on the host. Nothing is looked up before it. Restarting the server is a separate question |
Run thinkterm update, or press Install Update | GitHub: the release API, to find the newest release; the install script; and the release archive, which GitHub serves from its own storage after a redirect, or a mirror serves when GitHub is slow (see below) |
| Turn on browser access | Browsers that open a link you made |
| Turn on the built-in tailnet | Tailscale's control server, or the one you set under Control server, to join and stay joined; connections to machines in the tailnet go through it, and can be carried by Tailscale's relay servers |
Turn on Reach other machines for browser access, and open a machine from the page | The machine you name, over SSH from the server. If ThinkTerm has to be installed or updated there, the server copies its own programs across that connection when it can, and otherwise the host downloads the install script and the release from GitHub |
Install Update and thinkterm update run the install script in your environment. The script reads GITHUB_TOKEN if it is set, and sends it with its requests, but only if it has to download the archive itself. It does not when ThinkTerm has already fetched and checked the archive, which ThinkTerm does whenever GitHub recorded a checksum for it.
Wherever this page says the release archive, or on Windows the installer, comes from GitHub, it can come from a mirror instead: a host run by someone else that passes on GitHub's downloads. With Download source at Automatic, the default, and in the install script, a mirror is tried only when GitHub is slow or unreachable. It sees your address and which release you download, as GitHub would. The release lookup and the install script that Install Update and thinkterm update run always come from GitHub; a token is never sent to a mirror, and with one set no mirror is used at all; and a mirror is used only when GitHub recorded a checksum to check what it sends. GitHub (direct) under Settings › Software Update, or --mirror direct for the script, keeps every download on GitHub. When ThinkTerm installs or updates a remote server it fetches the install script and the release information itself, and sends them to the host over SSH. With Download source at Automatic those can come from a mirror as well, but only with a signature that checks against a key built into ThinkTerm; a script that fails the check is replaced by the copy bundled with ThinkTerm, and nothing unverified runs on the host. The host downloads the release archive itself, from GitHub or a mirror under the rules above. Update requests go through the system's proxy, or the one the environment names, when there is one.
Images in Notes load from the web by default, and loading one tells that server your address, as any web page would. note_remote_images_enabled = false stops it.
A plugin is a program you installed, and it can use the network like any other program. The Stocks plugin in ThinkTerm's source, for example, fetches quotes from Yahoo Finance. No plugin is part of the downloads; see Plugins.
The setup page shown the first time the app opens adds nothing to this list. It looks for Herdr sessions, a WezTerm config and the folders your code editors, coding agents and cmux last worked in, all on this computer, and sends none of it anywhere. It reads only what those programs have already written down: Herdr's session files, plus, except on Windows, a question over Herdr's own local socket about which sessions are running, the WezTerm config file, each editor's list of recent folders, and the records that Claude Code, Codex, Pi and cmux keep of the folders they worked in. From an agent's session files it takes the folder and the date, not what was said. It does not walk those folders to see what is in them, since that could make macOS ask for access to Documents or the Desktop before you chose to import anything.
What is kept on this machine
ThinkTerm keeps its state in your user account: Spaces, Projects and Threads, the SSH host book, snippets, recent commands, settings. Configuration lists where each lives on each system. A Notes Vault is a folder you chose, holding ordinary Markdown files.
Terminal output is held in memory, by the app or by the session server. One setting writes some of it to disk, and on the machine the desktop app runs on it is on by default: Settings › General › Restore pane contents. When ThinkTerm's terminals stop in an orderly way — a quit with the terminals inside the app, a shutdown or logout, the session server stopping — each local pane's main screen and scrollback, up to 10,000 rows, is saved to kept-output in the terminal-state directory, so that it can be put back above the new shell. A record is deleted once its layout is back, and after 15 days if the Thread is not reopened; turning the setting off deletes all of them. Before anything is written, text that looks like a password, a token or a private key is replaced with •, and links whose address holds one are dropped. That is pattern matching, and it cannot know every secret, so treat what a pane showed as being on disk in plain text. On a machine with no desktop app, such as a remote host, nothing is kept until thinkterm cli restore-output on is run there; restore-output off turns it off. See Configuration.
Settings › Workspaces › System Notifications is on by default. While ThinkTerm is in the background, it hands the system a notification when a Thread finishes, waits on you or stops on an error, holding the Thread's name, that state, and whatever the program running there said about it. From then on the system shows and keeps it like any other app's notification, which can include the lock screen. Turn the switch off if Thread names or what your programs report should not appear there, or take out the moments you do not want told: Finished, Needs You and Error are separate choices under the switch.
On macOS and Linux, the files that matter most are written readable by you alone, mode 0600:
secret.key, the key that encrypts saved SSH passwords- the snippets file
- the files of a backup you export
- a browser listener's token file and its certificate and private key
web_machines.json, the machines a browser page added and the passwords kept for them- the records
Restore pane contentskeeps, in a folder only you can open tailnet.json, the built-in tailnet's settings, with its auth key and node keys encrypted by the samesecret.key- a new Note ThinkTerm creates, unless the folder gives it a mode of its own
That stops other accounts on the machine. It does not stop anything running as you — a sync client, a backup job, a plugin — which reads a 0600 file as easily as ThinkTerm does. Remote explains what that means for saved passwords in particular.
thinkterm tailnet up --serve-ssh lets the machines in your tailnet reach this machine's own sshd, which sees them as connections from 127.0.0.1 or ::1, so any rule that trusts local connections applies to them too. Nothing else turns it on; see Remote.
Windows has no equivalent mode to set, so these files take the permissions of the folder they are in. For a default user profile that is you, SYSTEM and the Administrators group. To keep credentials out of a roaming profile, which Windows copies to a profile server at sign-out on some domains, the SSH host book, its key and web_machines.json are kept under %LOCALAPPDATA%\ThinkTerm\ssh\, and a browser listener's certificate under %LOCALAPPDATA%\thinkterm\web-tls\.
The browser page
Browser access is off until you turn it on. When it is on, the page, its scripts and its fonts are all served by your own machine; the page makes no request to anyone else, and nothing about its use is collected.
A browser is admitted by a token you mint, delivered as part of a link. The page takes the token out of the address bar as soon as it loads and keeps it in that tab's sessionStorage, which the browser discards when the tab closes. Whoever holds the link has a shell as you on that machine, so it is a credential like an SSH key.
The server stores tokens only if a token_file is configured; otherwise they live in memory and a restart forgets them. What it writes for each is a SHA-256 digest of the token, never the token itself — so a copied file admits nobody — with its name, when it was made, when it expires, when it was last used, and a coarse description of the browser that last used it, such as "iPhone · Safari". No address of any device is recorded, and the server's log names the token that was admitted rather than who used it.
A self-signed certificate is made only when a listener is bound off loopback without a certificate of your own. It names the machine's hostname and its addresses, which anyone who connects to that port can see — that is what a certificate is for.
The page keeps your choices for it — language, theme, font size, colour scheme, the Space it was showing, panel widths, recent palette picks, and the other machines it should reopen (under thinkterm.machines) — in the browser's localStorage, on that device. Clearing the site's data forgets them. Web covers who can reach the port and how to check the certificate.
The page can also open other machines through the server. That is off by default: Reach other machines in Settings › Web turns it on for every listener, and a server with no desktop sets relay = true on a listener in web_servers. Turning it off ends the machines already open within a couple of seconds. When it is on, the server is the one that connects, over SSH, with its own keys, the keys in its SSH agent and its own ~/.ssh/config. The hosts saved in the desktop's host book are offered too, and one with a saved password signs in with it without asking. So the page can reach whatever that server's SSH setup reaches, and can add any host and port of its own. A token already gives a shell on the server, from which the same keys could be used, so this adds no access of its own, but a leaked link with this on reaches those machines as well as the server. Questions the host asks — a password, whether to trust its host key, whether to install ThinkTerm there — are put to you in the page and answered through the server, and a password or key passphrase you ask to have kept is stored encrypted on the server, in web_machines.json next to the secrets the desktop saves. A password typed into the page's form for a new host is kept the same way, as is a yes to installing ThinkTerm on a host. The connection is not end-to-end: the SSH session ends at the server, not in the browser, so what passes between the browser and the host passes through it. Web has the rest.
Permission prompts on macOS
macOS attributes what a program does to the app that started it. ThinkTerm starts your shell, and your shell starts everything you run, so a command that touches something protected — find walking through the folder where Contacts are stored, a tool that opens the camera — produces a prompt that names ThinkTerm. The prompt's own text says this: An application launched via ThinkTerm would like to access…. ThinkTerm itself has no code that reaches for your contacts, camera, location or anything of the kind.
Local terminals kept in a background session server are started by that server, not by the app, and it asks under its own name, thinkterm-mux-server. A folder you already allowed for ThinkTerm may need allowing again for the server. Configuration describes how ThinkTerm tells you which of the two was refused.
On macOS, Settings › Permissions shows what those programs have before one asks: Full Disk Access, Screen & System Audio Recording, Microphone and Camera, each as allowed, not allowed, denied, not asked yet or unknown, read for whichever of the app and the server your terminals answer to. Looking never makes macOS prompt. Full Disk Access is the one to be careful with: once it is allowed, any program in a terminal can read and write other apps' data without macOS asking. See Configuration.
Links and pastes a program prints
A program in a terminal can print a link with any address behind any text, and text you paste may hold line breaks that run commands as it arrives. ThinkTerm asks before it acts on either.
A click on a link opens a web page, an http or https address, in your browser straight away. Any other address — a file, mailto:, ssh://, an app's own scheme, or text with no scheme at all — gets a small menu that shows the address first and offers Open in Another App or Cancel. An address that holds characters you cannot see, such as a zero-width space or a control that reverses the text's direction, is not opened at all: the menu spells them out as code points and says Not Opened: the Link Is Unsafe. Whatever ThinkTerm hands to the system's opener, a link or a path, is refused if it starts with a dash, since open and xdg-open would read it as an option; a file with such a name can be written ./-name or with its full path. ThinkTerm's log records that a link was opened, not its address.
Settings › Terminal › Ask before pasting line breaks is off by default. When it is on, a paste with a line break in it asks Paste Anyway or Cancel first, but only for a program that has not asked for bracketed pastes, which would run each line as it arrives. A shell such as zsh, which takes a paste as one block, is not asked about, and neither is a pane that cannot tell.
Plugins
A plugin runs as you, with your permissions. A new plugin stays off until you allow it — with Allow in Settings or the browser page's settings, or thinkterm plugin enable — and it asks again if the plugin is moved to another directory. Once allowed, ThinkTerm shows nothing of it outside the sidebar, but there is no sandbox around the program itself: it can read your files, use the network and run other programs, and on a terminal reached over SSH it can ask ThinkTerm to do the same on that host. Install one the way you would install any other program. Plugins has the details.
Reporting a security problem
Report it privately, through GitHub's private vulnerability reporting: open the repository's Security tab and choose Report a vulnerability, or go to github.com/RoversX/ThinkTerm/security/advisories/new. Please do not open a public issue, discussion or pull request about it.
Include as much of this as you can:
- the version, from
thinkterm -V, and your operating system - the part involved — the desktop app, the session server, browser access, SSH or Mosh connections, the plugin server
- steps to reproduce it, or a proof of concept
- what an attacker can do, and what they need first
You should hear back within a week. A fix for a complex problem can take longer, and you will be told how it is going. When the fix is released the advisory is published and credits you, unless you would rather not be named; until then, please keep the details private.
Only the latest release is supported, and fixes go into the next one. Check that the problem still happens there before reporting it.
What counts
All the code in the repository is in scope, including what ThinkTerm inherited from WezTerm. If a problem affects WezTerm as well, say so, so that it can be reported upstream too.
These are not vulnerabilities in ThinkTerm:
- What an installed plugin can do. It is a program you installed, running as you.
- Access by someone who already holds a valid browser token, can read your ThinkTerm data, or can log in as you. Tokens and the key behind saved passwords are credentials.
- Problems in programs ThinkTerm only runs, such as your shell,
sshormosh.
Builds from elsewhere
All of the above describes ThinkTerm's source and the builds its own release process publishes on GitHub. A build obtained anywhere else may have been changed to behave differently.
Related
- Install — what the install script downloads and checks
- Web — turning browser access on, and the link that admits a browser
- Plugins — what an installed plugin can reach
- Remote — saved SSH passwords and how they are protected
- Configuration — where ThinkTerm's files live
