安全
威胁模型
go-sequent 的威胁模型:资产、信任边界、威胁与缓解、明确的非目标,以及基于该库构建应用时的评审清单。
威胁模型
这是 go-sequent 面向生产加固的威胁模型,修订于 2026-10-26。它遵循锁定决策:hash chain(按聚合串联事件哈希,用来发现篡改)完整性、不做事件签名、仅 Tombstone 式 GDPR(不删历史事件,而是追加一条标记事件,让读模型不再暴露个人数据),以及无 HTTP 暴露面的纯库。
资产
| 资产 | 说明 |
|---|---|
事件库(events、commands) |
事实来源。正常运行时只追加。 |
| 唯一键与幂等键(同一业务键重复提交只成功一次) | 重试下的正确性 |
| 读模型表 | 可丢弃、可重建(Projector 把事件写成查询用读模型) |
| Outbox(事务内记待发消息,事后轮询投递) | 至少一次投递的缓冲 |
| 快照 | 性能缓存,以带版本的 JSON 存储 |
| Payload 中的 PII | 通过应用提供的 PayloadProtector 加密。GDPR 删除只通过 Tombstone 完成。 |
信任边界
- **客户端到应用。**认证发生在你的应用里。go-sequent 只把
Meta.AuthContext与UserID当作不透明的声明。没有经过验证的认证,绝不能信任客户端设置的UserID。 - **应用到
CommandService。**AuthZ 钩子(security.Authorizer)作为 Filter 或 Middleware,在 Handler 之前运行。 - **
CommandService到 PostgreSQL。**单写者主库。事件带 hash chain,你再加上数据库 ACL 与备份。没有事件签名。 - **SyncInTxn(写事务内同步更新读模型)Projector。**对账本而言,它们与追加在同一个写事务里。
- **Outbox 轮询器。**投递是至少一次,因此 Handler 必须可重复处理(Idempotency)。
威胁与缓解
| 威胁 | 缓解 |
|---|---|
| API 重放或重复提交 | 租户级幂等键与防御式 Handler |
| 通过伪造命令提权 | AuthZ 中间件。敏感命令类型使用 DenyByDefault。 |
| 事件库被篡改 | hash chain 校验,加数据库控制,可选 WORM 存储。不做签名。 |
| 跨租户访问 | meta 与行上的 tenant_id、tenancy.MatchAggregateTenant、tenancy.UniqueScope |
| 日志与 trace 中的 PII | 脱敏、PayloadProtector,绝不记录密钥 |
| Projector SQL 注入 | 只使用参数化的 Persistor |
| Workflow 或 Outbox 重复执行 | 幂等命令,加 Outbox ack 与去重 |
| 快照投毒 | 带版本的 JSON,由事件重新生成 |
| GDPR 删除 | Tombstone(标记清除事件)加 Projector 清理。加密销毁(crypto-shredding)不作为产品功能。 |
| 导入 Ruby Sequent dump | 永不 |
关于 hash chain
hash chain 能发现对已存储事件的修改,但无法阻止拥有写权限的人从修改点起重写整条链。因此要把它与数据库访问控制、受限的恢复权限和备份配合使用;如果监管要求,再加 WORM 存储。见 EventStore。
关于顺序
按当前的中间件顺序,幂等重放会在 AuthZ Filter 之前就得到响应。如果重放的命令也必须重新授权,请把授权写成在幂等之前运行的中间件。见 AuthZ 与 Idempotency。
明确的非目标
- 事件签名。hash chain 是框架内的完整性机制。
- 把加密销毁作为 GDPR 路径。
- 本仓库内的 gRPC 或 HTTP 服务。
应用评审清单
- 每一种会修改状态的命令类型都安装了 authorizer。
- 命令上总是设置租户,分区键以租户为作用域。
- 金额字段使用
types.Money,绝不使用float64。 - 账本余额由 SyncInTxn 处理,Outbox 只用于异步扇出。
- 已完成备份恢复演练,见 备份与恢复。
- 对任何改名的事件 payload 都注册了 upcaster。
相关
文档随 go-sequent 源码更新;若与 API 不一致,以源码为准。