# 安全与隔离

这一篇回答评估 Tokenman 时最先被问的两个问题——**会话能不能读到订阅凭据**、**别人能不能进我的工作区**——以及凭据放在哪、各活多久、哪一处被攻破会影响谁。

每一条都标了状态：**已上线** 是今天的 stable 里就有的；**计划中** 是已经定了、正在做的。标着「门槛 N」的，对应[公开前必须先完成的七项安全门槛](https://github.com/verkyyi/claude-fleet/blob/master/conf/public-gate.tsv)里的第 N 行；在接入入口的机器上跑 `fleet doctor`，`gate` 那几行逐项读出 PASS / FAIL。七项没全绿之前，我们不对外推广、不接托管客户。

## 两个问题的短答案

**会话能不能读到订阅凭据？不能。**（已上线 · 门槛 7）
托管机器上，Claude Code、Codex 的订阅凭据放在一个专门的角色账号名下（只有它能读的目录），会话所在的个人账号去读只会得到「Permission denied」。会话手里只有一张**会话通行证**：凭它请本机的凭据代理替它说话，最多活 4 小时，拿不到订阅凭据本身。所有托管登录都这样隔离。

**别人能不能进我的工作区？同一台机器上的其他成员不能。**（已上线）
每个人在机器上是一个独立的系统账号，家目录权限 `700`；会话就跑在这个账号里，所以一个人的会话读不到另一个人的文件、钥匙和会话。连进你账号的证书由入口只为你的登录签发；入口端口只认入口为你登记的钥匙。
例外要说清楚：**这台机器的管理员（有 root 或 sudo 的人）能看到这台机器上的一切**——这和任何一台共用的服务器一样。所以托管机器上的个人账号一律没有管理员权限，而每家托管客户有自己的一个入口，不和别家共用。

## 三条原则

1. **个人账号只是个人的。** 个人账号里只跑这个人自己的会话，不跑 Tokenman 的整机服务或管理服务。
2. **一个服务账号只干一件事。** 接收入口指令的、握着订阅凭据的、握着 root 的，是三个不同的账号；任何一个被攻破，拿到的只是它那一件事。
3. **root 只剩一个很小的核心。** 它不联网、不接外面的指令，只听本机节点守护的请求，只认一张写死的动作表；表外的请求一律拒绝并记下来。

## 一台托管机器上，谁跑什么

| 账号 | 跑什么 | 权限 | 状态 |
|---|---|---|---|
| root 核心 | 以某个登录的身份启停他的 Tokenman 进程、结束失控进程、内存保护、电源设置、复核签名后切换版本 | 不开端口、不读入口；只认本机节点守护，只认写死的动作表 | 已上线 · 门槛 2 |
| 节点守护 | 健康检查、磁盘巡检、上报、连入口收会话类指令、下载并验签新版本 | 没有 sudo，只能请 root 核心做表里的事 | 已可按机器开启，全部机器切换完成前为计划中 · 门槛 3 |
| 管理员程序 | 入口派来的开号、删号 | 只执行入口签过的单，一张单只能用一次、十分钟过期 | 「只认签过的单」已上线；搬进专门的管理员账号为计划中 · 门槛 4 |
| 凭据代理 | 握着订阅凭据，替会话说话 | 独立的角色账号，不和接外面指令的账号合并 | 已上线 · 门槛 7 |
| 每个人的账号 | 只跑这个人自己的会话 | 没有管理员权限、没有 sudo（`fleet doctor` 的 `tenants` 行，有一个就红） | 已上线 |

云上（入口所在的集群）同一套规矩：每个服务一个自己的服务账号，只给它要的权限，用户的 pod 不挂集群令牌——**计划中 · 门槛 5**。

## 凭据：放在哪、会话读不读得到、活多久

会话里跑着 Agent，也跑着它装进来的第三方代码（npm 包、MCP、仓库里的脚本）。会话能读到的东西，就可能被带出这台机器。我们挡不住每一次带出，但能让带出去的东西很快作废，所以规矩是：**会话读得到的一律短期，读不到的才长期。**

| 凭据 | 谁持有 | 会话读得到吗 | 有效期 | 怎么作废 | 状态 |
|---|---|---|---|---|---|
| 订阅凭据 | 凭据代理 / 入口的凭据库 | 不能 | 上游定 | 入口换掉账号；上游撤销 | 已上线 · 门槛 7 |
| 会话通行证 | 会话的环境 | 能 | 最长 4 小时，用到时剩不到 20% 才续 | 吊销名单，立刻生效 | 已上线 · 门槛 7 |
| 中转通行证 | 这个登录（隔离的登录里在角色账号名下） | 隔离的登录里不能 | 4 小时（入口配了中转签名钥匙时；没配的入口发的是不过期的旧通行证，门槛 7 读出红） | 吊销名单，立刻生效 | 已上线 · 门槛 7 |
| 机器身份、登录的节点令牌 | 节点守护 / 角色账号 | 不能 | 长期；出示时用自己的钥匙签名，不是一串可以照抄的令牌 | 入口吊销整台机器或这个登录 | 已上线 |
| 连接证书（人连进来用） | 人自己的电脑 | 托管机器上的会话读不到 | 12 小时，到期自动续；机器之间的只有 5 分钟 | 吊销名单 | 已上线；「连接前剩不到 20% 才续」为计划中 |
| 入口网页登录 | 人的浏览器 | 不能 | 7 天 | 入口按人整批作废 | 已上线；缩到 24 小时、打开时滑动续为计划中 |
| 发布签名私钥 | 今天在入口里 | 不能 | 长期，每年轮换 | 离线根钥匙签作废单 | 搬进发布流程、加离线根钥匙为计划中 · 门槛 1 |

**吊销立刻生效，入口不在也一样。**（已上线 · 门槛 6）入口把吊销名单推到中转和每台机器的入口端口，它们存在本地、每个请求只查本地这份；被吊销的通行证、证书即使没过期也立刻不能用。入口挂了，中转和门照旧用手上那份名单验证，已发出的短期凭据照常能用。

**自动更新只认签名。**（已上线）每台机器只拉 `stable`，用它记住的公钥验签；最后切换那一步由 root 核心自己再核一遍签名和每个文件的摘要。

## 哪一处被攻破，影响谁

| 被攻破的 | 能拿到 / 能做 | 拿不到 / 做不了 | 影响谁 |
|---|---|---|---|
| 一个人的账号或会话 | 他自己的家目录、钥匙、会话；他自己的会话通行证（最多 4 小时） | 别人的家目录、订阅凭据、sudo、root | 只有他自己 |
| 节点守护 | 看整机的进程和磁盘；请 root 核心按表启停某个登录的进程 | 读任何人的家目录、拿凭据、让 root 跑表外的命令、装没签过的版本 | 这台机器上所有人的可用性，不碰数据 |
| 管理员程序 | 拖住或拒绝这台机器的开号删号 | 没有入口签的单就开不了、删不了任何账号 | 这台机器上要开号删号的人 |
| 凭据代理 | 它管的那几个订阅账号 | 任何人的家目录和钥匙、root | 用这几个订阅的人（额度被盗用）；入口换掉账号即止血 |
| root 核心 | 这台机器上的一切 | — | 这台机器上所有人——所以它不联网、代码最小、只认写死的表 |
| 发布签名私钥 | 在所有机器上等同 root | — | 所有人——所以它要搬出入口、只在发布流程里（门槛 1，计划中） |

## 入口记了什么、谁能看

入口为每个重要操作记一行：谁（人、机器或会话）、什么时候、做了什么、结果——开号删号、签发和吊销凭据、机器加入和退出、改设置、会话的派发。只有这个入口的管理员能在控制台的审计页里看到。每家托管客户的入口是它自己的一个，记录不和别家混在一起。

每台机器的入口端口另记一行每次进门：哪个登录、从哪里来、用的哪把钥匙。

## 网页对话把你浏览器的什么交给模型

在官网 `/chat` 和自己的对话说话时，模型会知道你此刻用的是什么：设备类型（手机 / 平板 / 电脑）、系统、浏览器、界面语言、屏幕宽窄、本地时间和时区，以及按 IP 推出的大致城市（只到城市）——一行字，放在对话开头，换了设备、城市或日子才再补一行。完整的 IP、浏览器标识、网络类型只在模型需要时用 `browser_context` 工具取，只在你自己的这条对话里。

IP 和城市由入口看到、用入口本地的离线库推算，不问任何外部服务；这些都不写进日志，只留在你自己这条对话的记录里。

## 发现安全问题怎么报告

请不要开公开 issue。只走 GitHub 的私密漏洞报告：仓库页 → Security → [Report a vulnerability](https://github.com/verkyyi/claude-fleet/security/advisories/new)，不设邮箱。回复时限和支持的版本见仓库根目录的 [SECURITY.md](https://github.com/verkyyi/claude-fleet/blob/master/SECURITY.md)。
