fix(sync): secure encrypted snapshot lifecycle

This commit is contained in:
2026-07-10 06:24:53 -04:00
parent 556c5ff624
commit 540b901fd6
106 changed files with 18026 additions and 3309 deletions
+115 -62
View File
@@ -633,28 +633,27 @@ User Action
Local Encrypted Store
Sync Queue
Client-side Encryption
Snapshot Merge + XChaCha20-Poly1305 Encryption
Cloudflare Workers Sync API
D1 Object Index + R2 Encrypted Payload + KV Cache
D1 Global Head + Vault Metadata + R2 Ciphertext
Other Devices Pull Delta
Exact Head Token Download
Decrypt + Merge + Apply
```
当前 Cloud Sync 数据面使用 encrypted snapshot v3。`/api/sync/push``/api/sync/pull` 保留为认证后的退役端点,固定返回 `410 sync_object_protocol_retired`。D1 是会话、设备、Vault、head 与 GC 状态的权威源;KV 承担公开配置和历史数据清理。
### 9.2 登录能力
登录入口:
- Email + password。
- OAuth provider,可配置 Apple、Google、GitHub
- Passkey
- 设备二维码登录
- Recovery key 登录恢复。
- Email OTP:5 分钟有效、3 次尝试、重发轮换、服务端加密存储
- OAuth provider:当前支持按环境配置 Google、GitHub
- 目标能力:Apple、Passkey、设备二维码登录、Recovery Key 恢复
登录 UX
@@ -665,13 +664,12 @@ Decrypt + Merge + Apply
- 本地浏览器数据库密钥保存在系统钥匙串。
- 账号注销时可以选择保留本地数据或清理本地数据。
Better Auth 职责:
当前 Better Auth 职责:
- 用户注册、登录、邮箱验证、OAuth、Passkey、会话刷新、会话撤销
- 设备登录授权
- 账号删除流程
- 安全事件记录
- 与 D1 绑定,存储用户、账号、会话和验证相关表。
- Email/password、Email OTP、可选 Google/GitHub OAuth
- D1 authoritative bearer session,存储用户、账号、会话和验证记录
- ELY 自定义 device trust、Vault、Sync reset 与账号删除协议
- `better_auth_session_device_context` 将会话绑定到经过证明的设备身份
Desktop auth callback
@@ -712,25 +710,26 @@ Sync setup starts inside ELY settings
### 9.4 Sync 加密模型
密钥结构:
当前密钥结构:
```text
User Secret
├─ Account Recovery Key
Device Key Pair
└─ Sync Root Key
├─ Spaces Key
├─ Tabs Key
├─ Bookmarks Key
├─ History Key
├─ Settings Key
├─ Plugin Data Key
└─ Snapshot Key
Approved Device
├─ Ed25519 Signing Key
X25519 Wrapping Key
↓ HPKE envelope per approved device
AccountKey generation N
↓ HKDF-SHA256
Snapshot Encryption Key + Content Authentication Key
```
要求:
- Sync Root Key 在客户端生成
- 32-byte AccountKey 在客户端生成,服务器保存 `(key_id, generation)` 与每设备 envelope
- Envelope suite 固定为 `HPKE-BASE-X25519-HKDF-SHA256-CHACHA20POLY1305`
- Snapshot 使用 XChaCha20-Poly1305AAD 绑定 user、generation、snapshot、schema、logical clock、device 和完整 head lineage。
- 新设备先进入 pendingapproved v2 device 使用 recent Ed25519 proof 与 current-generation envelope 完成批准。
- 撤销 approved device 时,同一 D1 事务推进 Vault generation、写入剩余 approved v2 devices 的完整 envelope 集合、撤销目标会话并建立旧 R2 manifest。
- 新写入固定 `encryption_version=2`;legacy v1 仅承担历史解密与迁移。
- Better Auth 密码、OAuth 账号和服务器会话均不能直接解密 Sync payload。
- 新设备加入时,需要已登录设备批准、Recovery Key 或 Passkey + Recovery Key 组合。
- 密文 payload 使用 AEAD 加密。
@@ -738,6 +737,8 @@ User Secret
- D1 只存索引和小型密文;大型密文写入 R2。
- R2 object key 不直接暴露 URL、标题、站点名等敏感信息。
- 服务端日志不得记录 URL 明文、标题明文、书签明文、历史明文。
- 每个账户使用一个全局 snapshot head;上传通过 base head token 执行 CAS 推进。
- R2 写入先在 D1 建立带租约的写入记录,head 提交与引用状态在同一 D1 事务完成。
### 9.5 Sync 冲突处理
@@ -751,6 +752,12 @@ User Secret
处理方式:
- 每个账户只有一个 global snapshot head。
- Genesis 固定 `head_revision=1``base_head=null`
- 后继提交满足 `head_revision=base.revision+1`base 的 `(revision,snapshot_id,payload_hash)` 精确匹配 current headlogical clock 严格递增。
- CAS 冲突返回 `{version:1,error,current_head}` 的 structured `409`
- 完全相同的提交 replay 返回原 `201`R2 put 次数保持为零。
- GET 使用 `(snapshot_id,head_revision,payload_hash)` exact token;历史 token 返回携带 current head 的 `409`
- 有序列表使用 CRDT ordered list,保留稳定排序键。
- 普通对象使用 `updated_at + device_id + logical_clock` 解决。
- 不可自动合并的冲突进入 Conflict Center。
@@ -789,6 +796,8 @@ Settings / Sync
- 用户可以按对象类型暂停同步。
- 用户可以导出加密 Sync 备份。
- 用户可以从云端删除全部 Sync 数据。
- Sync reset 清除云端 Sync 对象、快照、head 和变更记录,保留当前 Vault generation、设备信任和设备密钥 envelope。
- `/api/sync/status` response v2 在一个 `first-primary` D1 batch 中读取 cursor、对象聚合、snapshot count/head 与 device summaryhead/count 或存储元数据失配时 fail closed。
---
@@ -801,10 +810,12 @@ ELY Desktop Client
↓ HTTPS
Cloudflare Workers API
├─ /api/auth/* Better Auth
├─ /api/sync/push Sync object upload
├─ /api/sync/pull Delta pull
├─ /api/sync/snapshot Snapshot upload/download
├─ /api/sync/push|pull Authenticated retired endpoints
├─ /api/sync/snapshot Encrypted snapshot v3 + global head CAS
├─ /api/sync/vault/* AccountKey envelope bootstrap/read
├─ /api/sync/reset Signed destructive action
├─ /api/devices/* Device management
├─ /api/account/delete Signed account deletion
├─ /api/plugins/* Plugin registry
├─ /api/releases/* Update manifest
└─ /api/telemetry/* Minimal diagnostics
@@ -812,9 +823,10 @@ Cloudflare Workers API
Cloudflare D1
├─ better_auth_* tables
├─ user_devices
├─ sync_objects
├─ sync_change_log
├─ sync_snapshots
├─ user_device_keys + device trust
├─ sync_snapshots + sync_snapshot_heads
├─ sync_vault_* + rotation manifests
├─ sync_r2_gc_candidates + inventory cursors
├─ plugin_registry
├─ plugin_reviews
└─ audit_events
@@ -829,7 +841,7 @@ Cloudflare R2
Cloudflare Workers KV
├─ public_config
├─ auth_session_cache
├─ auth_session_cache legacy cleanup namespace
├─ plugin_registry_cache
├─ public_signing_keys
├─ release_manifest_cache
@@ -847,6 +859,9 @@ D1 存储:
- Sync 对象索引:对象 ID、对象类型、owner、payload 位置、hash、schema、时间戳。
- Sync 变更日志:用于增量拉取。
- Sync 快照索引:指向 R2 snapshot object。
- Snapshot encryption metadata 与 global head。
- AccountKey Vault generation、per-device HPKE envelope 与 rotation manifest。
- R2 write lease、引用状态、删除状态与 inventory cursor。
- 插件注册表元数据:插件 ID、名称、作者、权限声明、签名状态、包位置。
- 审计事件:登录、设备加入、设备撤销、权限变更、插件安装、Sync reset。
@@ -862,10 +877,10 @@ D1 不存储:
D1 schema 设计要求:
- 所有用户数据表必须有 `user_id`
- 所有 Sync 事实表必须有 `object_id``object_type``logical_clock``device_id`
- 所有可删除对象必须有 `deleted_at` tombstone
- 所有增量拉取必须通过 `sync_change_log` 读取。
- 用户作用域表使用 `user_id`;账号删除后的 R2 ledger 使用不可逆 `owner_hash` 收尾
- Snapshot/Vault/head/GC 表使用各自的严格 key、generation、revision 与状态约束
- Snapshot 删除使用 hard delete + durable R2 ledger;对象协议表保留历史 migration compatibility
- D1 batch 提供原子 CAS 与 destructive action gate`first-primary` session 提供顺序一致读取。
- 所有 D1 写入必须可幂等重放。
- 单个 D1 数据库接近容量阈值时按地区或账户拆分。
@@ -887,7 +902,7 @@ R2 object key 规范:
```text
sync-payloads/{region}/{user_hash}/{object_type}/{object_id}/{payload_hash}.bin
sync-snapshots/{region}/{user_hash}/{snapshot_id}.bin
sync-snapshots/{region}/{user_hash}/{snapshot_id}/{payload_hash}.bin
plugin-packages/{plugin_id}/{package_hash}.rplug
plugin-assets/{plugin_id}/{asset_hash}
user-avatars/{user_hash}/{avatar_hash}
@@ -899,9 +914,14 @@ exports/{user_hash}/{export_id}.bin
- 所有 Sync payload 上传前在客户端加密。
- R2 metadata 不写敏感明文。
- Worker 只签发短期上传/下载访问
- Worker 经认证 API 直接执行 checksum-verified R2 put/get
- 插件包必须通过签名验证后才进入 registry 可见状态。
- 用户删除账号时触发 R2 对象清理任务。
- Sync R2 对象使用 D1 ledger 跟踪 `pending``referenced``ready``deleting``deleted` 状态。
- 定时任务扫描 `sync-payloads/``sync-snapshots/` 历史对象并重试幂等删除。
- 账号删除提交后清除 ledger 中的原始 user ID,使用不可逆 owner hash 继续完成 R2 清理。
- Snapshot 写入先领取 64-hex write token 与 10 分钟 leasecandidate、encryption、head、referenced state、exact head SELECT 在五语句 D1 batch 中提交。
- Hourly cron `17 * * * *` 分别执行 legacy KV purge、R2 prefix inventory、bounded GC 与 rotation cleanup;完整 prefix 重扫周期为 24 小时。
### 10.4 Cloudflare Workers KV 使用边界
@@ -910,11 +930,11 @@ KV 是缓存和读多写少配置存储。
KV 存储:
- `public_config`:公开运行配置、服务端开关、地区路由提示。
- `auth_session_cache`短期会话验证缓存
- `auth_session_cache`legacy namespace,进入分页清理流程
- `plugin_registry_cache`:插件市场列表缓存。
- `public_signing_keys`:插件签名公钥、服务端公钥。
- `release_manifest_cache`:自动更新清单缓存。
- `sync_cursor_cache`:短期同步游标缓存。
- `sync_cursor_cache`规划中的短期同步游标缓存。
KV 使用规则:
@@ -924,24 +944,25 @@ KV 使用规则:
- KV 缓存可随时失效,所有关键数据必须可从 D1/R2 重建。
- KV key 设计必须包含 namespace 和环境前缀。
- KV TTL 必须按用途设置,默认不得无限期保存短期状态。
- 定时任务分页清理历史 `auth_session_cache` key。
- 所有认证请求直接读取 Better Auth D1 sessionKV 不参与 session authority。
### 10.5 Better Auth 集成
Better Auth 在 Cloudflare Workers 中初始化,D1 binding 作为 database 传入。
能力要求
当前能力:
- Email/password 注册登录。
- OAuth provider 配置
- Passkey 支持
- 邮箱验证
- 会话刷新
- 会话撤销
- 设备登录授权。
- 安全事件记录。
- 账号删除。
- Encrypted Email OTP
- Google/GitHub OAuth 按环境启用
- D1 session validation 与自定义 device/session binding
- 设备注册、rebind、批准、撤销与 Vault rotation
- Signed Sync reset 和 signed account deletion
- 管理所有 `/api/auth/*` 路由。
目标能力包括 Apple OAuth、Passkey、Recovery Key 与完整服务端 session revoke UX。
会话策略:
- Access session 短期有效。
@@ -949,6 +970,8 @@ Better Auth 在 Cloudflare Workers 中初始化,D1 binding 作为 database 传
- 服务端可以撤销单设备或全部设备会话。
- Desktop client 每次启动执行 session validation。
- 高风险操作要求重新验证。
- Sync reset 与账号删除 request v2 要求 5 分钟内的 Ed25519 action proofproof 绑定 action、user、session、device、confirmation、idempotency key 与创建时间。
- Destructive D1 batch 首条执行 live session/device/key gateguard failure 与 concurrent replay 触发事务 abort,后续通过 exact audit marker 收敛。
### 10.6 API 规格
@@ -959,19 +982,26 @@ Auth
| `/api/auth/*` | Any | Better Auth handler |
| `/api/devices` | GET | 当前账号设备列表 |
| `/api/devices/register` | POST | 注册当前设备公钥 |
| `/api/devices/rebind/challenge` | POST | 为未绑定的新会话签发短期 challenge |
| `/api/devices/rebind` | POST | 用现有 Ed25519 device key 绑定新会话 |
| `/api/devices/approve` | POST | 批准新设备加入 |
| `/api/devices/revoke` | POST | 撤销设备 |
| `/api/account/delete` | POST | request v2 recent proof + atomic gate 删除账号 |
Sync
| Endpoint | Method | 说明 |
|---|---|---|
| `/api/sync/push` | POST | 上传对象变更 |
| `/api/sync/pull` | GET | 按 cursor 拉取增量 |
| `/api/sync/snapshot` | POST | 上传加密快照 |
| `/api/sync/snapshot` | GET | 下载加密快照 |
| `/api/sync/reset` | POST | 删除云端 Sync 数据 |
| `/api/sync/status` | GET | 获取队列和游标状态 |
| `/api/sync/push` | POST | 认证后返回 `410` 的退役对象协议 |
| `/api/sync/pull` | GET | 认证后返回 `410` 的退役对象协议 |
| `/api/sync/snapshot` | POST | request/response v3 encrypted snapshot CAS |
| `/api/sync/snapshot` | GET | exact head token 下载 encrypted snapshot |
| `/api/sync/vault/bootstrap` | POST | request v2 proof 创建 generation 1 Vault |
| `/api/sync/vault` | GET | 读取当前或 exact historical device envelope |
| `/api/sync/reset` | POST | request v2 recent proof + atomic gate 删除云端 Sync 数据 |
| `/api/sync/status` | GET | response v2 cursor/object/snapshot/device summary |
`/api/sync/reset` 与账号删除响应中的 `deleted.r2_objects` 表示已识别并进入持久清理队列的 R2 对象数;物理删除由请求内回收和定时重试共同完成。
Plugin
@@ -997,11 +1027,24 @@ Better Auth 表由 Better Auth schema 管理。ELY 自定义表如下:
| 表 | 用途 |
|---|---|
| `user_devices` | 用户设备、公钥、平台、活跃状态 |
| `user_device_keys` | Ed25519/X25519 public key 与 protocol version |
| `device_approvals` | 新设备加入批准记录 |
| `device_rebind_challenges` | 短期 session rebind challenge 与消费状态 |
| `better_auth_session_device_context` | Better Auth session 与 device identity 绑定 |
| `sync_objects` | Sync 对象索引 |
| `sync_change_log` | Sync 增量日志 |
| `sync_snapshots` | Sync 快照索引 |
| `sync_snapshot_encryption` | Snapshot encryption version、generation、key/content hash |
| `sync_snapshot_heads` | 每账户唯一 global snapshot head |
| `sync_tombstones` | 删除标记 |
| `sync_vault_accounts` | 当前 AccountKey id 与 generation |
| `sync_vault_envelopes` | Per-device HPKE AccountKey envelope |
| `sync_vault_rotations` | Device revoke Vault rotation 状态 |
| `sync_vault_rotation_envelopes` | Rotation recipient exact set |
| `sync_vault_rotation_r2_objects` | Rotation 旧 R2 manifest |
| `pending_device_revocations` | Pending device revoke 幂等记录 |
| `sync_r2_gc_candidates` | R2 write lease、reference 与 GC state machine |
| `sync_r2_inventory_cursors` | R2 prefix inventory cursor |
| `plugin_registry` | 插件注册表 |
| `plugin_packages` | 插件包和签名 |
| `plugin_reviews` | 插件审核记录 |
@@ -1303,6 +1346,7 @@ Servo Host 需要实现:
- Sync 密文与账号会话分离。
- 本地密钥与云端会话分离。
- 高风险操作有显式确认和审计日志。
- Sync reset 与账号删除使用 current-device Ed25519 recent proofD1 commit 原子复核 live session、session-device binding、approved v2 device 与 exact signing key。
### 13.2 隐私默认值
@@ -1331,6 +1375,8 @@ Servo Host 需要实现:
审计日志默认只保存在本地,可选择端到端加密同步。
服务端 `audit_events` 记录 device approval/revoke、Sync reset 与账号删除。账号删除保留 anonymized success markermarker 绑定 exact proof hash,使已认证的并发请求在 device/key 清理后仍能安全收敛。
---
## 14. 性能指标
@@ -1522,14 +1568,16 @@ Servo Host 需要实现:
|---|---|---|
| S-001 | Sync encryption | 服务端无法解密 Sync payload |
| S-002 | Keychain | session token 和本地数据库密钥存入系统钥匙串 |
| S-003 | Device revoke | 撤销设备后无法继续拉取 Sync delta |
| S-003 | Device revoke | 撤销设备后目标 sessions 失效、后续 Sync API 被拒绝、approved revoke 原子轮换 Vault generation |
| S-004 | Plugin sandbox | 插件无声明权限时无法访问 tabs/bookmarks/history/page |
| S-005 | Plugin signature | 市场插件必须签名验证通过 |
| S-006 | Site permissions | 所有站点权限按 Profile 隔离 |
| S-007 | Private Window | 关闭后无持久 Cookie、Storage、History |
| S-008 | Audit log | 高风险操作全部写入本地审计日志 |
| S-009 | Cloud logs | 服务端日志不得记录敏感明文 URL、标题、历史、书签 |
| S-010 | Account deletion | 删除账号触发 D1/R2/KV 数据清理 |
| S-010 | Account deletion | Signed request 原子删除 D1 权威数据;R2 ledger 与 legacy KV cleanup 持久排队并由请求内 drain + scheduled retry 收口 |
| S-011 | Snapshot CAS | 单一 global head、exact base CAS、structured 409、exact replay zero R2 put |
| S-012 | Destructive proof | Stolen bearer 缺少 device private key 时无法执行 Sync reset 或账号删除 |
### 19.3 性能验收
@@ -1658,6 +1706,9 @@ KV namespace: ELY_KV
- Cloudflare Worker API tests。
- D1 migration tests。
- R2 upload/download tests。
- Real SQLite CAS、rollback 与 proof-after-session-revoke interleaving tests。
- R2 write lease、late put、101+ drain、inventory、delete retry tests。
- Legacy encryption metadata backfill 与 structured `409` contract tests。
- KV cache invalidation tests。
- Cross-platform smoke tests。
- Servo site compatibility smoke tests。
@@ -1715,8 +1766,10 @@ KV namespace: ELY_KV
- D1 write failure。
- R2 upload/download failure。
- KV cache hit rate。
- Sync push/pull latency。
- Sync conflict rate。
- Snapshot upload/download latency。
- Snapshot head conflict rate。
- R2 pending/ready/deleting backlog 与 inventory cursor age。
- Scheduled storage maintenance failure 与 Vault rotation cleanup lag。
- Device revoke events。
- Plugin install failure。
- Plugin signature failure。