SECURITY

Security.

The three technical promises in our privacy policy — credentials never leave system secure storage, sync is end-to-end encrypted, no tracking — spelled out here in a form you can check. Every section ends with "How to verify": you do not have to take our word for it.

Last updated: 2026-09-29

01 · CREDENTIALS

Credentials live only in system secure storage.

Credentials means passwords, private keys, GitHub / Gitee access tokens (PATs), sign-in tokens, and the sync key when you choose to remember it. They are written only to the secure storage built into your operating system; CloverTerm implements no encryption of its own for storing them.

The local database cloverterm.db holds only a random ID (a reference) for each — columns such as password_ref, key_ref and pat_ref. The ID points to an entry in secure storage and contains no secret itself. With keyboard-interactive authentication, your answers to each server prompt (passwords, verification codes) are used for that connection only — never saved, never logged.

System secure storage by platform

SecureStorage
macOS
The login keychain (ADR-0008).
iOS
The Keychain, at the "this device only, when unlocked" accessibility level — entries never sync to other devices through iCloud Keychain.
Windows
DPAPI, encrypted for the current Windows user.
Android
Data encrypted with AES-GCM, with that key in turn wrapped by an RSA key held in the Android Keystore.
Linux
libsecret (the Secret Service — typically GNOME Keyring).
02 · SYNC ENCRYPTION

Sync is end-to-end encrypted; your repository only ever sees ciphertext.

With self-hosted sync on, your local data — hosts, passwords and private keys, snippets, trusted host fingerprints and more — is packed into a snapshot, encrypted on your device, and only then pushed to your own private GitHub / Gitee repository. The synced data is a single file, cloverterm-sync-v1.enc; when the app creates the repository for you, it also adds a README.md explaining what the repository is for.

Where the key comes from: your sync password goes through Argon2id on your device (64 MiB of memory, 3 passes, 4 lanes) to derive a 256-bit key, which encrypts the snapshot with AES-256-GCM using a fresh random nonce every time. The sync password is used only on your device to derive that key — it is never uploaded and cannot be recovered from any server. With "Remember on this device" ticked, what goes into secure storage is the derived key, not the password. No encryption can rescue a guessable password, so make it a long one.

A separate random salt for every sync space (ADR-0026): the first device to turn on sync generates a 16-byte salt from the system's secure random source and writes it, together with the key-derivation parameters, into the file header. The same sync password therefore yields a different key for every person, so nobody can precompute one table of common passwords and try it against everyone's ciphertext at once.

The header (format marker, key-derivation parameters, salt) is not secret, but all of it is covered by AES-256-GCM's integrity check: change a single byte and the file no longer decrypts — the app reports an error and imports nothing. Parameters read from the remote are bounded: memory or pass counts below the defaults are rejected outright.

Your access token (PAT) likewise lives only in system secure storage. Resetting the sync password uploads a snapshot encrypted with the new key, but earlier snapshots stay in the repository's Git history (encrypted with the old key) and are not cleaned up automatically. Official sync (Cloud, not yet available) uses the same encryption: the server, too, only ever stores this ciphertext.

03 · HOST KEY

Host fingerprints: confirmed the first time, checked every time after.

The first time you connect to a server, the app shows its public-key algorithm and SHA-256 fingerprint (formatted SHA256:<base64>, the same as OpenSSH) and lets you choose Trust & Save, Trust Once or Reject. This is known as TOFU — trust on first use.

Every later connection is checked automatically: a matching fingerprint connects; a different one stops the connection and shows the old and new fingerprints side by side, warning of a possible man-in-the-middle attack (or a legitimately reinstalled server). You must tick "I acknowledge this change is expected" before you can Update & Trust; closing the dialog counts as rejecting.

The connection core rejects by default: if the prompt never reaches you (a timeout, or no screen to show it on), that counts as a rejection too; an OpenSSH host certificate is refused outright, as there is no certificate authority to check it against. After a rejection, no authentication data is sent — no username, no password, no public key — and the connection closes immediately.

Trust records are managed by the app itself and matched by address and port; you can view, copy and remove them on the Fingerprints page, and with sync on they travel inside the encrypted snapshot. The app does not read your system's ~/.ssh/known_hosts (ADR-0003).

04 · NO TELEMETRY

No telemetry: two kinds of report, every field listed here.

No behavioural tracking, no advertising identifiers, and no third-party analytics, advertising or crash-reporting SDK — error reporting is our own and goes through no third party (ADR-0012). Logs are never uploaded automatically; "Export Diagnostics" builds a report on your device only when you click it, and you decide whom to send it to.

The app sends us only the two kinds of report below, and a single switch — Settings › Support › Usage stats & error reports, on by default — controls both. Turn it off and neither is sent any more, error reports still waiting on your device are cleared, and no local feature is affected. Requests made by account features themselves — sign-in, feedback and the like — also carry the app version and distribution headers.

Usage stats

UsageStatsCan be turned off
Sent to
POST /api/v1/heartbeat on the CloverTerm cloud service, once per launch.
Fields
The request body has exactly four fields: install_id (an installation ID generated at random on first launch, with no hardware information in it), platform (macos / windows / linux / ios / android), app_version, and distribution (where the installer came from, e.g. macos-web). The request headers X-Install-Id, X-App-Version and X-Distribution carry the same values.
Signed in
The request also carries your sign-in token, which lets the cloud file this device under your account; the device name appears only after you sign in and confirm the device. While you are signed out, the cloud only knows that such a device exists.
Purpose
Counting active devices (devices with a heartbeat in the last thirty days).

Error reports

ErrorReportCan be turned off
Sent to
POST /api/v1/errors, generated only when an unhandled error occurs inside the app (the app keeps running afterwards); crashes and freezes are out of scope. Reports are queued on your device and sent once online.
Fields
app_version, platform, error_type, message (the redacted first line of the message, at most 500 characters), stack_frames (only frames from CloverTerm's own code, at most 30) and occurred_at (when it happened). The request headers carry the installation ID (which the cloud uses for rate limiting), app version and distribution, plus your sign-in token when you are signed in.
Redaction
Done on your device before a report is queued: user@host, IP addresses, domain-plus-port, absolute paths, and the names, addresses and usernames of your saved hosts are replaced with a placeholder. No logs, terminal content or commands.
Server-side
Reports are deduplicated and counted by error type plus top stack frame; the stored record holds no installation ID and no account. A record not seen again for ninety days is deleted.
05 · INSTALLERS

Installers: get them from official sources, then check them yourself.

The only official domains are cloverterm.cn and cloverterm.com, and the iOS app installs from the App Store; do not install packages from any other site.

The download page lists a SHA-256 next to every installer we host. The packaging script computes it once the installer is built, it is written into the release manifest at release time, and the download page shows it from there.

Android packages are signed only with our release key: the build fails outright if that key is missing and never falls back to a debug key. The certificate's SHA-256 fingerprint is 00:20:FB:5B:4C:6E:E9:C4:19:CA:DE:09:C3:F5:97:00:11:71:C1:9F:C2:EB:1B:63:F5:5F:DE:57:15:B7:1A:23.

The in-app update check only notifies and never downloads or installs anything itself: "Update" opens the download link in your system browser (see point 5 of the audit below).

06 · SOURCE CODE

The client is not open source, so everything checkable is on this page.

The CloverTerm client is not open source. "Why trust closed source?" is a fair question, and this page is our answer: where credentials live, how sync is encrypted, what gets reported — each with a way to verify it that needs no source code.

The probe — a future program that would run on your servers and only read and collect data — has not been released and may be open-sourced later; the future probe will hold no power to execute commands.

The open-source components the client uses, and their licences, are listed under Third-party licences.

07 · PRE-LAUNCH AUDIT

Pre-launch security audit: five findings.

From the vulnerabilities that similar SSH tools have disclosed over the past three years, we picked five classes and checked our own code against each. The findings, each guarded by automated tests:

  1. 01When a host fingerprint is rejected (by the user, or because the user could not be asked), no authentication data is sent and the connection closes immediately.
  2. 02Passwords, private keys, tokens and sync keys exist only in system secure storage; the local database holds only references; there is no home-made encryption; sync uses Argon2id + AES-256-GCM with a separate random salt for every sync space.
  3. 03Sync snapshots and import files are treated as untrusted input: malformed data only produces an error and leaves local data untouched, fields are checked for type and length, and nothing is ever treated as a database statement or file path (five fixes made before launch).
  4. 04When downloading, previewing or sharing over SFTP, a file name supplied by the server cannot write outside the target folder (fixed).
  5. 05The update check reads the release manifest over HTTPS only, only notifies and never installs automatically, and accepts only https download links (fixed); there is no probe today, and the future probe will hold no power to execute commands.

// "ADR-00xx" numbers refer to our internal architecture decision records. Related: privacy policy · download · third-party licences · FAQ