SECURITY

安全说明。

隐私政策里的三条技术承诺——凭据不出系统安全存储、同步端到端加密、不做追踪——在这里展开成你能自己核对的说明。每一节末尾都有「怎么自己验证」:不必信我们的话,照着做一遍就知道。

最近更新:2026-09-29

01 · CREDENTIALS

凭据只进系统安全存储。

凭据指密码、私钥、GitHub / Gitee 访问令牌(PAT)、登录令牌,以及你选择记住时的同步密钥。它们只写进操作系统自带的安全存储;CloverTerm 没有自己实现任何加密来存它们。

本机数据库 cloverterm.db 里只存一个随机编号(引用),比如 password_ref、key_ref、pat_ref 这几列;编号指向安全存储里的条目,本身不含任何秘密。认证方式选「键盘交互」时,服务器每一轮提示的回答(密码、验证码)只在当次连接里用,不保存、不进日志。

各平台的系统安全存储

SecureStorage
macOS
登录钥匙串(login keychain,ADR-0008)。
iOS
钥匙串,访问级别为「仅本设备、解锁后可读」,不随 iCloud 钥匙串同步到别的设备。
Windows
DPAPI,按当前 Windows 用户加密。
Android
数据用 AES-GCM 加密,这把密钥再由 Android Keystore 里的 RSA 密钥包裹。
Linux
libsecret(Secret Service,常见的是 GNOME 钥匙环)。
02 · SYNC ENCRYPTION

同步端到端加密,仓库里只有密文。

开启自托管同步后,主机、密码与私钥、代码片段、已信任的主机指纹等本机数据先打成一份快照,在本机加密,然后才推到你自己的 GitHub / Gitee 私有仓库。同步数据只有一个文件 cloverterm-sync-v1.enc;app 替你新建仓库时,还会放一份说明用途的 README.md。

密钥怎么来:你设的同步密码在本机经 Argon2id(64 MiB 内存、3 次迭代、4 路并行)派生出 256 位密钥,再用 AES-256-GCM 加密快照,每次加密都用新的随机 nonce。同步密码只在本机用于派生,不上传,也无法从云端找回;勾选「记住本机」时,存进系统安全存储的是派生出的密钥,不是密码本身。加密再强也挡不住一个好猜的密码——同步密码请设长一点。

每个同步空间一份独立的随机盐(ADR-0026):第一台设备开启同步时,用系统的安全随机源在本机生成 16 字节的盐,连同派生参数写进文件头。同一个同步密码在不同人那里派生出不同的密钥,没人能预先算好一张常见密码的密钥表,拿去一次撞所有人的密文。

文件头(格式标记、派生参数、盐)本来就不是秘密,但它整段参与 AES-256-GCM 的完整性校验:改动任何一个字节,文件都解不开,app 只报错、不导入。从远端读到的派生参数有上下限,内存或迭代次数低于默认值的一律拒绝。

访问令牌(PAT)同样只在系统安全存储里。重设同步密码会上传一份用新密钥加密的快照,但旧快照仍留在仓库的 Git 历史里(用旧密钥加密),不会自动清理。官方同步(Cloud,尚未开放)用同一套加密,服务器上存的也只是这份密文。

03 · HOST KEY

主机指纹:第一次确认,之后每次核对。

第一次连一台服务器时,app 显示它的公钥算法与 SHA-256 指纹(格式为 SHA256:<base64>,与 OpenSSH 一致),由你选「信任并保存」「仅本次信任」或「拒绝」。这种做法叫 TOFU(首次使用时信任)。

之后每次连接都自动核对:指纹一致才连;不一致就停下,并排显示原指纹与新指纹,提示可能有中间人攻击,也可能是服务器重装了——要先勾选「我已确认这是预期变更」才能「更新并信任」,直接关掉对话框等于拒绝。

连接内核默认拒绝:确认框没能交到你面前(超时、界面没接上)也按拒绝处理;服务器出示 OpenSSH 主机证书时直接拒绝,因为没有证书机构可以核对。被拒之后不发送任何认证信息——用户名、密码、公钥都不发——连接立即关闭。

信任记录由 app 自己管理,按地址与端口匹配,在「指纹」页可以查看、复制、移除;开了同步会随快照一起加密同步。app 不读系统的 ~/.ssh/known_hosts(ADR-0003)。

04 · NO TELEMETRY

没有遥测:只有两类上报,字段全列在这里。

没有行为埋点,没有广告标识符,也没有接入任何第三方统计、广告或崩溃分析 SDK——错误上报是自建的,不经第三方(ADR-0012)。日志从不自动上传;「导出诊断信息」只在你点了之后在本机生成,发给谁由你决定。

app 主动发给我们的只有下面两类,由设置 › 支持 ›「使用统计与错误上报」一个开关管着,默认开。关掉之后两类都不再发送,本机还没发出去的错误报告也会清掉,本地功能不受任何影响。另外,登录、反馈这类账号功能自己的请求,也都带着应用版本与分发渠道两个请求头。

使用统计

UsageStats可关闭
发往
CloverTerm 云端服务的 POST /api/v1/heartbeat,每次启动一次。
字段
请求体只有四个字段:install_id(首次启动时随机生成的安装 ID,不含任何硬件信息)、platform(macos / windows / linux / ios / android)、app_version(应用版本)、distribution(分发渠道,即安装包从哪来,如 macos-web)。请求头 X-Install-Id、X-App-Version、X-Distribution 带的是同样的值。
登录后
请求会带上你的登录令牌,云端据此把这台设备归入你的账号;设备名只在你登录并确认这台设备之后出现。没登录时,云端只知道「有这样一台设备」。
用途
统计活跃设备(近三十天内有过心跳的设备)。

错误报告

ErrorReport可关闭
发往
POST /api/v1/errors。只在程序内部出现未处理的错误时生成(出错后 app 仍在运行),闪退、卡住无响应不在范围内;先写进本机队列,联网后再发。
字段
app_version、platform、error_type(错误类型)、message(脱敏后的消息首行,最多 500 个字符)、stack_frames(只留 CloverTerm 自己代码的堆栈帧,最多 30 条)、occurred_at(发生时间)。请求头同样带安装 ID(云端用它限频)、应用版本与分发渠道,登录时另带登录令牌。
脱敏
入队之前就在本机处理:消息里的 user@host、IP、域名加端口、绝对路径,以及你保存的主机名、主机地址、用户名,一律替换成 <已脱敏>。不含日志、终端内容与命令。
服务器上
按「错误类型 + 最顶上那一帧」去重计数,存下的记录里没有安装 ID,也没有账号;九十天没再出现的记录自动删除。
05 · INSTALLERS

安装包:认准官方来源,下载后自己核对。

官方域名只有 cloverterm.cn 与 cloverterm.com,iOS 版从 App Store 安装;其它站点的安装包一律不要装。

下载页在每个官网安装包旁列出 SHA-256。这个值由打包脚本在安装包打好之后计算,发版时写进版本文件,下载页照着显示。

Android 安装包只用我们的发布密钥签名:构建时找不到这把密钥就直接失败,不会退回调试密钥。证书的 SHA-256 指纹是 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。

app 里的更新检查只提示、不自己下载安装:点「更新」是用系统浏览器打开下载链接(见下面自查第 5 条)。

06 · SOURCE CODE

客户端不开源,能核对的都写在这一页。

CloverTerm 客户端不开源。「没开源凭什么信」是个正当的问题,我们的回答就是这一页:凭据存在哪、同步怎么加密、上报了什么,每一条都附了不需要源码就能做的验证方法。

探针(将来装在你服务器上、只读采集数据的程序)还没有推出,后期可能开源;将来的探针不持有执行权。

客户端用到的开源组件与许可证见第三方许可证。

07 · PRE-LAUNCH AUDIT

首发前安全自查:五条结论。

我们从同类 SSH 工具近三年公开过的漏洞里挑出五类,逐条核查了自己的代码。结论如下,每一条都有自动化测试守着:

  1. 01主机指纹被拒(用户拒绝或系统没能问到用户)后不发送任何认证信息、连接立即关闭。
  2. 02密码、私钥、令牌、同步密钥只存在系统安全存储,本地数据库只存引用,没有自己实现的加密,同步用 Argon2id + AES-256-GCM、每个同步空间独立随机盐。
  3. 03同步快照与导入文件按不可信输入处理:格式不对只报错、本机数据不动,字段按类型与长度校验,不会被当成数据库语句或文件路径(首发前修了五处)。
  4. 04SFTP 下载、预览、分享时服务器给的文件名写不出目标文件夹(已修)。
  5. 05更新检查只经 HTTPS 读版本文件、只提示不自动安装、下载链接只认 https(已修);现在没有探针,将来探针不持有执行权。

// 文中的「ADR-00xx」是我们内部架构决策记录的编号。相关页面:隐私政策 · 下载 · 第三方许可证 · 常见问题