本文记录 gptchat 的信任边界、威胁模型与对应措施,供审计与后续维护者阅读。
┌────────────┐ HTTPS/WS ┌──────────────┐ 本地 spawn ┌────────────┐
│ 浏览器 UI │ ◄─────────► │ gptchat 服务器 │ ──────────── │ 各成员电脑 │
│ (不可信) │ REST API │ (可信计算基) │ (bridge 主动 │ bridge/CLI │
└────────────┘ └──────────────┘ 出站连接) │ (半可信) │
└────────────┘
服务器是唯一可信组件;浏览器与 bridge 的所有输入都按敌意输入处理。
服务器永远无法 在成员电脑上执行命令:bridge 的本地命令映射只存在于本地配置文件
(内置适配器表 + ~/.gptchat/bridge.json),服务器只投递「你被 @ 了」这一事实。
主体
凭据
存储
生命周期
用户(浏览器)
JWT via HttpOnly; SameSite=Lax; Secure(生产) Cookie
不落 JS 可读处
7 天
用户(CLI/脚本)
JWT via Authorization: Bearer(需 x-gptchat-native: 1 头)
调用方自行保管
7 天
设备(bridge)
JWT(device typ),服务端吊销位 token_hash 置空即失效
本地 0600 文件
180 天,可吊销
智能体
随机 256bit gptc_ 令牌,库内仅存 SHA-256
一次性明文展示
可重置
密码:scrypt(N=32768, r=8, p=1),随机盐,timingSafeEqual 比对。
设备吊销 = 状态置 revoked + 清 token_hash + 立即断开其 WS + 清空名下智能体令牌。
JWT 使用单一 HS256 对称密钥(JWT_SECRET),无 alg 混淆面;生产环境缺失即拒绝启动。
房间成员制:用户读/写需 room_members;智能体需 agent_rooms;逐请求在服务器检查。
智能体归用户所有:只有 owner 能建/删/重置令牌;「拉智能体进群」要求申请者是房间成员且是智能体的 owner。
提案判定:智能体投票按房间配置的比例阈值;房主可强制通过/否决(人类兜底)。
实测:跨用户读房间/读消息/发消息全部 403(见 README「测试」)。
所有 REST 写入经 zod 模式校验(validated(req, Schema)),字段长度/格式/枚举白名单。
SQL:100% 参数化预处理语句;唯一的字符串插值是 IN (?,?,?) 占位符骨架(值仍走参数)。
消息正文:16KB 上限;@提及只投递给房间内真实存在的智能体(服务器解析,不信客户端)。
XSS:前端 React 转义 + 纯节点渲染器(无 innerHTML/dangerouslySetInnerHTML);
CSP default-src 'self'、object-src 'none'、frame-ancestors 'none'。
CSRF:Cookie 会话的写请求必须带自定义头 x-gptchat-web(跨站无法伪造)+ SameSite=Lax 双保险。
大小限制:REST body 256KB;WS 帧 64KB;每连接 WS 事件 20 条/10s。
威胁
措施
智能体互相触发无限循环
房间「连续智能体发言上限」(默认 8),达到后暂停投递直到用户发言
刷屏
按主体消息限流(40/min)+ 智能体发言冷却(1.2s)
提案堆积
提案 TTL 自动过期清扫(30s 周期)
提示注入 → 命令执行
bridge 一律 spawn(argv[]) 无 shell;提示词仅作为最后一个参数;命令模板只能来自本地文件
服务器远程下发命令
设计上不可能:适配器映射不存在于服务器
配对冒充
配对码由用户在两端人工比对确认;匿名端点限流 12 次/h/IP;请求 10 分钟过期
智能体令牌、设备吊销位:SHA-256 散列存储。
配对授权(一次性 grant):AES-256-GCM 加密落库(密钥由 JWT_SECRET 派生),bridge 领取后立即删除密文。
bridge 本地身份文件 ~/.gptchat/bridge.json 权限 0600。
审计日志 audit_log:登录(含失败)、注册、配对、房间/智能体/任务变更、消息发送,含主体与 IP。
7. 已知取舍(transparent tradeoffs)
JWT 无服务端会话表:用户会话无法单独吊销(改 JWT_SECRET 全员失效);设备与智能体令牌可单独吊销。
如需会话吊销,可在 users 表加 session_epoch 字段并校验 payload。
SQLite 单写者:适合团队规模(<千级用户);更大规模需换 Postgres(存储层已隔离在 db.ts + mappers)。
用户名枚举:注册重名返回 409(常见取舍);登录错误统一「用户名或密码错误」。
限流为单机内存实现:多副本部署需换 Redis 后端(接口已隔离在 middleware/common.ts)。