Skip to content

终结式命名空间通配已修四次、发现四次都靠人手 —— 缺的是枚举,不是又一个 per-plugin 测试 #4116

Description

@os-zhuang

按 Prime Directive #10 记录。#4088 / #4092 与 cloud#923 收尾时提出。

现象

挂在通配路径上的 handler(app.all('/api/v1/auth/*', h))声明了整个命名空间。Hono 对同一路径按注册顺序依次执行 handler,先产出 Response 的赢。所以只要那个 handler 是终结式的(总是应答、从不 next()),同前缀下其他插件的路由就只有"恰好注册得更早"才能被访问到 —— 而插件注册顺序既没人声明、也没人测试。

这个形状已经付了四次代价,四次都是有人为了别的事读代码时偶然发现的

事故 后果
#2567 raw /data 的匿名拒绝只在 REST 先注册时成立 —— 加载顺序一变,匿名数据访问被静默重新打开
#4018 standalone /discovery 被 dispatcher 的遮蔽,于是第三份供给长期漂移无人察觉
#4088 / #4092 AuthPlugin/api/v1/auth/* 吞掉 plugin-hono-server/auth/me/permissions(console 的整个权限层),除非 server 插件先注册
cloud#923 AuthProxyPlugin 同一形状。而且 cloud 已经真的踩过apps/{objectos,cloud}/server/index.ts 记着 staging 上 Console 404 on /api/v1/auth/me/permissions

CI 对这四个一个都没拦住,对第五个也不会。

为什么不是再写一个 per-plugin 测试

#4092 与 cloud#923 各自带了一个驱动测试,钉住那一个 catch-all 的落穿。这些值得有,但它们结构上不可能覆盖下一个通配 —— 测试只能驱动它被写下那天已经存在的路由。

从来不存在的是枚举。 "这个仓库里有哪些 handler 声明了命名空间、每一个是不是终结式" 这个问题一直没有答案,所以新的终结式 catch-all 落地时没有任何东西会失败。

建议:一个全仓 AST 扫描 + 三态 ledger

check-route-envelope.mjs#3843)的既有形状做,包括最关键的那条:扫到但未登记 = 错误,而不是默认放行。正是"默认放行"让上面四个攒起来的。

三态,与 #3843 同构:

  • yields —— handler 收 next 并调用它。由 AST 验证,所以条目不会腐烂:把 next() 删掉就会失败。
  • exempt —— 附理由,给那些本就应该独占整个命名空间的(传输适配器的单一入口、SPA 静态子树、第三方中间件工厂)。
  • ratchet —— 当前是终结式、被跟踪、未被祝福:记当前状态 + 将修它的 issue。

没有第四种"没人看过"的状态。

必须用 AST 而非正则:"这个 handler 调了 next() 吗"是关于参数绑定的问题,不是关于文本 next( 附近出现过。注释里提到 next()、字符串里提到、或者内层闭包自己的 next —— 三者都能骗过文本匹配,而且是朝"通过"的方向骗,缺陷照样发货。参数名也不一定叫 next

已经扫出来的东西

原型扫描在 packages/ 下找到 13 处命名空间挂载(手工 grep 当初只找到 3 处),其中两处是真实的、未被跟踪的同类缺陷:packages/adapters/hono/src/index.ts${prefix}/auth/*${prefix}/storage/* 都是终结式。该适配器全仓无消费者,所以今天没坏东西,但它是发布出去的包 —— 详见随后的独立 issue。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions