6.9 KiB
ADR-0215:实例 Owner 围栏化、有界的 Local Security Audit 查询
- 状态:Accepted
- 日期:2026-07-29
- 关联 RFC:QL-RFC-0001 D-05、D-27、D-37、D-65、D-72、D-73、D-175、 D-198、D-200、D-201、D-203、D-204、D-205
- 关联 ADR:ADR-0049、ADR-0074、ADR-0208、ADR-0211、ADR-0213、ADR-0214
背景
QingLong 3.0 的本机管理入口已经为 Secret、Identity、Credential、Project 和 RoleBinding 的允许、拒绝与最终围栏失败写入 durable security audit,但部署者仍没有 受支持的读取入口。直接查询 SQLite 会把内部 schema 变成产品 API,绕过强认证、 authority、字段脱敏和有界资源契约。
审计记录横跨整个实例,可能包含 Project 拓扑、subject、操作结果和认证关联信息。
它不是普通的 Project-scoped 业务数据,任意 secondary Project Owner 不应因为拥有
自己的 project.manage 就能枚举其他 Project 的安全事件。
决策
1. 在既有 package 内增加独立 subpath 和短生命周期 CLI
新增:
@qinglong/runtime-core/local-security-audit-query;@qinglong/local-sqlite/security-audit-query;@qinglong/local-admin/security-audit-query;@qinglong/local-owner-cli/security-audit-query-command;- 一次命令、一次进程的
ql3-auditbinary。
这些是已有 package 内的能力边界,不是新的 workspace package。查询不进入常驻 application,不创建 daemon、timer、watcher、listener、连接池、缓存或端口。SQLite contract 保持 v37,workspace 保持 22 个 package。
2. 只有实例 authority Project 的当前强认证 User Owner 可以查询
request 必须携带 authorityProjectId。local-admin 固定以 project.manage 请求
Owner-only decision;SQLite 在同一个 BEGIN IMMEDIATE 内重新验证:
- authenticated credential、Identity、有效期和 pepper provenance;
- ADR-0211 instance authority anchor;
- authority Project 的 active/version;
- actor 最新 active Owner RoleBinding/version。
secondary Project Owner、admin/operator/viewer、非 User principal 和 foreign authority Project 全部失败关闭。服务授权后 credential、anchor、Project 或 RoleBinding 漂移时,最终事务拒绝且不得提交 allowed audit。
3. 查询形状固定且有界
security.audit.list 复用共享 security audit query 的精确语义:
limit为 1–64;共享 Cluster 上限 200 不改变本机更严格上限;- filter 只允许可选
projectId、精确subject、精确outcome; - cursor 精确为
{occurredAtMs,eventId}; - 排序固定为
(occurredAtMs DESC,eventId DESC); - SQLite 读取
limit + 1,只有实际存在额外记录才返回nextCursor。
禁止 offset、任意排序、模糊搜索、调用方 SQL、无界数组或历史全量 export。每一页是 一次独立授权、独立审计的 SQLite snapshot,不承诺跨 command 的同一时点视图。
4. 查询与自己的 allowed audit 原子提交
SQLite 先在事务内完成 authority 复验和查询,再写本次 security.audit.list allowed
audit,最后 commit。因此返回的 snapshot 不包含本次查询事件;查询成功和审计写入
仍保持原子性。
翻页必须使用新的 request ID 和 audit event ID。查询不是 mutation receipt;不得以 重放旧 audit event ID 的方式请求下一页。
5. 产品输出必须脱敏
ql3-audit 只输出:
- event/request/operation identity;
- nullable Project;
- nullable subject;
- outcome、reasons、nullable policy fence;
- occurred timestamp;
- nullable next cursor。
即使 durable row 中存在,也不得输出 authenticationId。同样不得输出 credential、
pepper、Secret、路径、command 内容或数据库内部字段。认证失败和最终围栏失败只写
低敏 failure audit。
不采用方案
直接开放只读 SQLite 或通用 SQL
拒绝。它无法约束字段、行数、排序、authority 或审计,并把 schema 演进变成外部兼容 承诺。
让每个 Project Owner 查询自己 Project 的原始审计
拒绝。当前 security audit 还包含 nullable/global 和实例管理事件,没有独立的 Project 安全视图、行级投影或字段级 redaction contract。需要时应另行设计 Project-local projection,而不是弱化实例审计 authority。
一次返回全部记录或实现 export
拒绝。Edge 路由设备的内存、闪存和终端输出必须有硬上限。合规 export、签名、保留期 和销毁证明是后续独立能力,不能伪装成大页查询。
新建 audit package 或常驻管理服务
拒绝。该能力与既有 runtime contract、单 SQLite authority、local-admin 和 Owner CLI 共享部署生命周期;新 package/daemon 只会增加低配设备的供应链和空闲成本。
影响
正向影响:
- 部署者不再需要直接读取 SQLite 才能诊断拒绝和围栏失败;
- 实例级审计 authority 与 Project-scoped RoleBinding authority 明确分离;
- 64 条硬上限、keyset 和精确 filter 约束内存、CPU 与输出;
- 同事务 final fence 防止 credential/Policy TOCTOU;
- CLI 删除
authenticationId,避免认证关联标识进入终端或脚本输出; - 不新增 migration、package、生产依赖或常驻资源。
代价与限制:
- 每页查询都会短暂取得 SQLite write reservation,以原子写查询审计;
- 当前不提供 operation filter、时间范围、聚合、export、retention、签名或告警;
- 每页是独立 snapshot,翻页期间的新事件可能改变后续结果;
- Cluster 继续使用独立 PostgreSQL management authority 和更高的共享页上限。
验证
- GitNexus:共享
normalizeSecurityAuditQuery上游风险 LOW;高风险LocalSqliteRunRepository(17 个直接、45 个总上游)未修改,D-205 使用独立 repository/composition; - D-205 五个新源文件通过隔离 strict TypeScript,启用
exactOptionalPropertyTypes与noUncheckedIndexedAccess; - 定向测试 8/8:
- instance Owner 授权与 filter/cursor 透传;
- limit 65 在 repository 前拒绝;
- non-Owner denial audit;
- service 后 final policy/credential fence conflict 保留;
- 真实 SQLite
limit + 1、两页降序 keyset 和末页 null cursor; - query snapshot 不包含自己的 allowed audit;
- foreign instance authority 在读取/allowed audit 前拒绝;
- CLI 删除
authenticationId并审计 credential fence failure;
- SQLite contract 保持 v37,workspace 保持 22 package;
- 完整 package closure 与 PostgreSQL HA Docker 门仍需在锁定依赖物化后重跑。
后续状态
ADR-0216 已在 SQLite v38 完成实例 Owner 围栏化、64/512 条硬上限的诊断审计压缩。 它只回收无已知引用的拒绝/失败事件和精确只读查询事件;完整领域 retention、签名 export、远端归档与合规销毁证明仍未完成。