Files
qinglong/docs/adr/ADR-0048-reviewed-cluster-route-registry.md
T

5.5 KiB
Raw Blame History

ADR-0048: Cluster 受审 Route Registry 与授权输入边界

  • 状态:Proposed
  • 日期:2026-07-19
  • 关联 RFCQL-RFC-0001 D-04、D-08、D-13、D-27 至 D-32、D-43 至 D-46

上下文

ADR-0046 已固定 route→Authentication→Policy→security audit→body→handlerADR-0047 也提供了 PostgreSQL Project Policy adapter。但 admission 当时仍接受任意 ClusterControlRouteResolver:resolver 可以在运行时根据未受审逻辑产生 operation、permission、Project ID 和 handler。类型检查不能证明该 resolver 没有从 query/header 自报 permission、依赖注册顺序选择重叠 route,或把 Project scope 延迟到 body 中决定。

对 cluster-control,这不是普通 router 的可替换实现细节,而是 Policy 的可信输入边界。路由表必须在 readiness 后开放流量之前完成验证;无权请求必须在低资源 edge gateway 或 cluster ingress 读取 body 前终结。

决策

1. Route definition 是启动时受审静态事实

@qinglong/cluster-control/routes 公开工厂,definition 必须显式提供:

  • HTTP method 与 canonical /api/v3/... path template
  • 全局唯一 operation ID
  • Project Policy permission
  • Project ID 对应的显式 path parameter,或明确的 null global scope
  • query name allowlist
  • domain handler。

operation、permission 和 Project scope 不得由 header、query、body 或 handler 返回值决定。null scope 在当前 Project Policy adapter 下默认 deny;未来全局 operation 必须使用独立受审 Policy,而不是绕过 Project Policy。

2. Registry 编译阶段拒绝歧义和无界配置

registry 在启动时:

  • 最多接收 256 条 route、每条最多 16 个 segment 和 8 个 path parameter
  • 禁止 wildcard、catch-all、可选 segment、尾随/重复斜杠和 percent-encoded template
  • 拒绝相同 method 下可能匹配同一 path 的 static/parameter 重叠;
  • 拒绝重复 operation ID、重复 parameter、未知字段和未声明的 Project parameter
  • 固定最多 16 个 allowlisted query name。

拒绝重叠是为了让匹配结果不依赖注册顺序。新增 route 必须在 code review 中同时审阅 operation、permission、scope 和 handler。

3. Admission 只接受工厂产生的 Registry

admission 不再只做结构化鸭子类型检查。模块内部使用不可复制的 registry identity 验证 resolver;伪造相同 contractVersion/resolve shape 仍在装配时被拒绝。这样 application stack 不能静默注入临时 allow route 或 handler-owned resolver。

registry 仍返回原有 prepared route contract,使 Authentication、Policy、audit 和 HTTP body/drain 边界无需耦合具体业务 router 或框架。

4. 非规范请求在 Authentication 前拒绝

运行时匹配只接受安全 ASCII segment。以下输入返回稳定低敏 400,且不调用 authenticator、Policy、audit 或 handler

  • percent-encoding path alias
  • 重复/尾随斜杠、反斜杠或控制字符;
  • 超长 path、过多 segment
  • 未 allowlist query
  • query 重复值、单值大小或控制字符超过硬边界。

canonical 但未注册的 method/path 返回 route_not_found,同样发生在 Authentication 前。

被否决的替代方案

继续接受任意 RouteResolver port

拒绝。可替换 resolver 对普通路由器是扩展点,对 Policy 输入却是 authority seam;任何调用方都可产生未受审 permission 或 Project scope。

复用 Express/Umi router metadata

拒绝。会把 cluster artifact 重新耦合到 2.x root/web framework,并且不能证明 middleware 注册、path normalization 与 Policy route 一一对应。

允许 wildcard 后按注册顺序选择

拒绝。新增 static route 可能被旧 wildcard 抢先匹配,使代码评审看到的 permission 与运行结果不同。

从 JSON body 提取 Project ID

拒绝。Policy 前读取 body 破坏两阶段 admission,也让攻击者先消耗解析内存。需要 Project scope 的 operation 必须把稳定 identity 放在 path 中。

影响

正向

  • operation/permission/Project scope 从 handler 建议值提升为启动时验证的安全事实;
  • 路由结果不依赖注册顺序或 URL 编码别名;
  • 未知与畸形请求在认证和 body 读取前有界终结;
  • registry package 入口不加载 legacy root、PostgreSQL migration DDL 或 Drizzle schema。

代价

  • 动态插件不能直接向核心 /api/v3 注入任意 route;插件 action 必须经过独立 tool/action registry、Policy 和 manifest gate
  • 同一 operation 的多个 HTTP alias 需要不同 operation ID 或显式重构,不能共享一个模糊审计身份;
  • 当前 ADR 只完成安全 registry 机制;后续 ADR-0049 已补齐真实 credential authenticator 与持久化 audit sink,但 production business route、credential 管理面和 audit retention/query 仍未完成。

验证

  1. registry contract test 覆盖 immutable scope、handler parameter、伪造 registry 拒绝和 source mutation 隔离。
  2. configuration test 覆盖 widened shape、重复 operation、static/parameter overlap、无效 Project parameter 和 route 数量硬上限。
  3. request test 覆盖 percent traversal、重复/尾随 slash、反斜杠、unknown query、重复 query bound 与 control character。
  4. admission test 证明未知 route 不调用 authenticator,伪造 resolver 在 assembly 时失败。
  5. Node 22/24、公开入口 import closure、cluster dependency audit 与 GitNexus impact/detect-changes 继续作为门禁。