formatting and comments
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
# 安全模型
|
||||
|
||||
本服务同时处理 Bilibili 登录 Cookie、TOTP Secret、一次性恢复码、管理员邀请码和 OBS
|
||||
token。以下规则是实现约束,而不是可选部署建议。
|
||||
|
||||
## 租户隔离
|
||||
|
||||
- HTTP handler 只从服务端会话解析 `owner_id`,不接受客户端声明的 owner。
|
||||
- 组件查询同时限定 `owner_user_id` 与 `source_id`。
|
||||
- 数据库使用 owner 复合外键、RLS 和 `FORCE ROW LEVEL SECURITY`。
|
||||
- tenant 查询必须在事务中执行 `SET LOCAL app.user_id`,不能使用会泄漏到连接池的 session-level
|
||||
`SET`。
|
||||
- 实时广播按 `component_id` 建立独立 channel,不提供全局订阅。
|
||||
- 路由器在投影发布前再次验证 owner、source 和 component ID。
|
||||
|
||||
## Secret 生命周期
|
||||
|
||||
| Secret | 浏览器可见性 | 数据库存储 | 轮换/消费 |
|
||||
| -------------------- | ------------------- | ----------------------- | ---------------- |
|
||||
| TOTP Secret | 注册时显示一次 | XChaCha20-Poly1305 密文 | 账户绑定 |
|
||||
| 恢复码 | 注册完成时显示一次 | SHA-256 摘要 | 单次消费 |
|
||||
| 登录 session | HttpOnly Cookie | SHA-256 摘要 | 到期、登出或撤销 |
|
||||
| CookieCloud Key/密码 | 用户提交时 | XChaCha20-Poly1305 密文 | 覆盖更新 |
|
||||
| 邀请码 | 创建时显示一次 | SHA-256 摘要和前缀 | 单次消费或撤销 |
|
||||
| OBS token | 创建/轮换时显示一次 | SHA-256 摘要 | 组件级轮换 |
|
||||
|
||||
`security.data_encryption_key`
|
||||
是恢复密文所必需的主密钥。它必须独立备份,但不能提交到 Git 或写入镜像。
|
||||
|
||||
## Passwordless 认证
|
||||
|
||||
- 第一个系统管理员只能通过数据库为空时的 bootstrap 流程创建。
|
||||
- bootstrap proof 不成为账户密码,也不能用于日常登录。
|
||||
- TOTP 接受有限时钟偏移,并持久化最近使用的 time step,阻止同一码重放。
|
||||
- 登录与匿名注册同时按账户维度和网络维度限流,错误消息不暴露用户名是否存在。
|
||||
- 注册先写入短期 pending enrollment;只有正确 TOTP 确认后才原子创建账户并消费邀请码。
|
||||
|
||||
## CookieCloud 与 SSRF
|
||||
|
||||
- 部署者通过 `cookiecloud_allowed_hosts` 指定精确的基础地址白名单。
|
||||
- URL 会规范化,并拒绝 embedded credentials、query 和 fragment。
|
||||
- Key 编码成单一路径段,不能注入额外路径。
|
||||
- HTTP 客户端禁止重定向,防止允许的地址跳转到内网目标。
|
||||
- 浏览器只看到 host 和 `keyConfigured`/`passwordConfigured`,不会读回凭据。
|
||||
- 日志不得把 `blivedm` 调到可能打印认证响应的详细级别。
|
||||
|
||||
## HTTP、WebSocket 与 OBS
|
||||
|
||||
- 公开部署必须在受信任反代终止 HTTPS,并保持 `secure_cookies = true`。
|
||||
- 生产 session 使用 `__Host-` Cookie、HttpOnly、Secure 与 SameSite 策略。
|
||||
- 写 API 执行 same-origin 检查并限制 body 大小。
|
||||
- 所有 `/api/*` 响应使用 `Cache-Control: no-store`。
|
||||
- OBS token 放在 URL fragment,并作为 WebSocket 第一帧发送。
|
||||
- token 只具有 `events:subscribe` scope,且只能订阅其绑定的组件。
|
||||
- WebSocket 首帧、frame size、认证时间和全局连接数均有上限。
|
||||
|
||||
## PWA 边界
|
||||
|
||||
- manifest、Service Worker 和 start URL 都限定在 `/control/`。
|
||||
- `/obs/*` 不在 Service Worker scope 内。
|
||||
- worker 只缓存静态应用壳层、图标和构建资源;不缓存 API、WebSocket 或直播事件。
|
||||
- 离线 mutation 直接失败,不使用 Background Sync。
|
||||
- 新 worker 仅在用户确认后激活。
|
||||
- TOTP、恢复码、邀请码、新 OBS 地址或未保存设置可见时,更新会被阻止。
|
||||
|
||||
## 上线检查表
|
||||
|
||||
- [ ] 使用独立随机 `data_encryption_key`,并在安全位置备份。
|
||||
- [ ] `config.toml` 权限为 `0600`,且已被 Git 和 Docker build context 排除。
|
||||
- [ ] PostgreSQL runtime role 可以执行迁移,但 PUBLIC 无权执行 SECURITY DEFINER helper。
|
||||
- [ ] `secure_cookies = true`,Nginx 正确传递 `X-Forwarded-Proto https`。
|
||||
- [ ] 应用仅监听 loopback,9719 未直接暴露公网。
|
||||
- [ ] CookieCloud 白名单只包含管理员批准的实例。
|
||||
- [ ] 日志中没有 Cookie、TOTP、token、邀请码或上游认证响应。
|
||||
- [ ] 轮换 OBS token 后,旧浏览器源立即断开且无法重连。
|
||||
Reference in New Issue
Block a user