安全与隔离
这一篇回答评估 Tokenman 时最先被问的两个问题——会话能不能读到订阅凭据、别人能不能进我的工作区——以及凭据放在哪、各活多久、哪一处被攻破会影响谁。
每一条都标了状态:已上线 是今天的 stable 里就有的;计划中 是已经定了、正在做的。标着「门槛 N」的,对应公开前必须先完成的七项安全门槛里的第 N 行;在接入入口的机器上跑 fleet doctor,gate 那几行逐项读出 PASS / FAIL。七项没全绿之前,我们不对外推广、不接托管客户。
两个问题的短答案
会话能不能读到订阅凭据?不能。(已上线 · 门槛 7) 托管机器上,Claude Code、Codex 的订阅凭据放在一个专门的角色账号名下(只有它能读的目录),会话所在的个人账号去读只会得到「Permission denied」。会话手里只有一张会话通行证:凭它请本机的凭据代理替它说话,最多活 4 小时,拿不到订阅凭据本身。所有托管登录都这样隔离。
别人能不能进我的工作区?同一台机器上的其他成员不能。(已上线) 每个人在机器上是一个独立的系统账号,家目录权限 700;会话就跑在这个账号里,所以一个人的会话读不到另一个人的文件、钥匙和会话。连进你账号的证书由入口只为你的登录签发;入口端口只认入口为你登记的钥匙。 例外要说清楚:这台机器的管理员(有 root 或 sudo 的人)能看到这台机器上的一切——这和任何一台共用的服务器一样。所以托管机器上的个人账号一律没有管理员权限,而每家托管客户有自己的一个入口,不和别家共用。
三条原则
- 个人账号只是个人的。 个人账号里只跑这个人自己的会话,不跑 Tokenman 的整机服务或管理服务。
- 一个服务账号只干一件事。 接收入口指令的、握着订阅凭据的、握着 root 的,是三个不同的账号;任何一个被攻破,拿到的只是它那一件事。
- 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,不设邮箱。回复时限和支持的版本见仓库根目录的 SECURITY.md。