test(plugin-auth): enumerate better-auth's route table — the /auth/** wildcard becomes 55 exact rows (#3656) - #3669
Merged
Conversation
…*` wildcard becomes 55 exact rows (#3656) The widest hole the #3642 capstone measured. That guard reports how many SDK calls match only a `**` prefix family rather than a resolvable route, and the answer was 60 of ~196 — 54 of them on `* /auth/**`, the largest and most security-relevant namespace in the client. `auth.me` builds /api/v1/auth/get-session; a prefix claim cannot tell you better-auth still calls it that, and better-auth is a third-party dependency on its own release cadence (this repo already chased its 1.7 column drift in #3624 / #3647). plugin-auth mounts it with ONE catch-all — rawApp.all(`${basePath}/*`) — so there are no per-route registration calls to capture the way tranche 3 captured registerStorageRoutes. The seam is `auth.api`: every better-auth endpoint carries .path and .options.method, so a live instance IS the route table. auth-route-ledger.ts reads it, in two halves checked differently on purpose: - 55 REVIEWED ROWS — every route the SDK calls, each naming its client method, checked strictly against the live table. The rename detector. - 129-PATH MOUNTED-SURFACE INVENTORY — exact equality both ways, so a version bump that adds publicly-mounted auth endpoints becomes a reviewable CI diff. The asymmetry is a decision: demanding a hand-written rationale for all 129 would turn every better-auth upgrade into a hundred-row review and the ledger would rot into rubber-stamping. But the catch-all publishes whatever upstream adds, so growth still has to be visible. Enumeration is config-dependent (better-auth plugins are opt-in), so the inventory is pinned at the configuration enabling every plugin the SDK targets — the maximal surface — with the participating OS_* env vars cleared so a developer's shell cannot produce a spurious diff. Mutation-checked: renaming a ledgered route fails the suite naming it. The capstone guard now includes this ledger in its union AND prefers exact rows over wildcard families when matching — without that ordering fix every /auth/* URL would still be absorbed by `* /auth/**` and the new ledger would have changed nothing. Wildcard-only matches fall 60 → 3 and the ratchet moves with them; what remains is `* /ai/**`, whose routes service-ai builds at plugin start. No runtime change: a ledger, a guard, and the header/audit-doc notes. Refs #3656, #3642, #3563. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K35y3ovfWtCkBHYFCqUfAt
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 3 package(s): 31 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
os-zhuang
marked this pull request as ready for review
July 27, 2026 13:54
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #3656 —— 补上 #3642 收口守卫量出来的最大那个洞。
这个洞有多大
#3642 那个守卫会数:有多少 SDK 调用只靠
**前缀族匹配上、而不是靠一条能解析的路由。答案是 196 条里 60 条,其中 54 条压在* /auth/**上——客户端里最大、也最跟安全相关的那个命名空间。auth.me拼的是/api/v1/auth/get-session。前缀声明只能告诉你"/auth底下有人认领",没法告诉你 better-auth 现在还叫不叫这个名字。而 better-auth 是个按自己节奏发版的第三方依赖——本仓库刚在 #3624 / #3647 追过它 1.7 的列漂移,这不是假想风险。枚举的口子在哪
plugin-auth是用一条 catch-all 挂的:所以没有逐路由的注册调用可捕获,tranche 3 那种"对着 mock
IHttpServer跑注册器"的做法在这里不适用。真正的口子是
auth.api:better-auth 的每个端点对象都带.path和.options.method,所以一个活实例本身就是路由表。不是手写清单,是从上游读出来的。两半,故意用不同强度检查
AUTH_ROUTE_LEDGER—— 55 条过审行,SDK 实际调用的每条路由,各自写明客户端方法auth.me打/get-session,一旦上游改名,26 个auth.*方法集体 404,而此前没有任何东西会发现BETTER_AUTH_MOUNTED_SURFACE—— 129 条 wire path,完整清单这个不对称是决定,不是疏忽。 要求 129 条都写一句人工过审的理由,会让每次 better-auth 升级变成一百多行的复审,账本很快就腐烂成橡皮图章。但 catch-all 会把上游新增的一切都公开出去,所以"长出新端点"仍然必须可见——于是用机器维护的清单 + 相等断言,让它变成一个必须有人看的 CI diff,而不是一段过审散文。
两个容易踩的细节
枚举结果依赖配置。 better-auth 插件是 opt-in 的,
two-factor/*在默认配置下根本不存在(我第一次枚举就是这么发现的:135 条 vs 103 条)。清单钉在"SDK 会用到的插件全开"这个配置上——最大面,保证 SDK 能碰到的路由一条都不缺。同时把参与决策的OS_*环境变量在测试期间清空,免得开发者 shell 里导了个OS_SSO_ENABLED就看到一堆假 diff。/config和/bootstrap-status不是 better-auth 的。 这两条是auth-plugin.ts自己挂在 catch-all 之前的,所以按构造就不会出现在auth.api里。它们用source: 'objectstack'区分,并且有一条断言钉死"只有这两条能用这个 source、且它们确实不来自上游"——免得这个分类变成"上游检查没过就塞这里"的后门。对 #3642 守卫的影响
通配符匹配 60 → 3,只剩
* /ai/**(service-ai 在插件启动时构建)。棘轮同步下调。这里还需要一个配套修正:让匹配时精确行优先于通配符族。不改这个的话,
/auth/*的 URL 仍会被排在前面的* /auth/**吸收掉,新账本等于白加——这点我在代码注释里写明了。做了变异测试:把某条账本行改名,守卫立刻失败并点名 ——
验证
auth-route-ledger.conformance.test.ts7 项通过;变异测试确认可失败turbo run test --filter=@objectstack/plugin-auth --filter=@objectstack/client --filter=@objectstack/runtime—— 29/29eslint --no-inline-config、check-nul-bytes、check-doc-authoring、check-role-word均通过无运行时改动:一份账本、一个守卫,以及账本头部与审计文档(新增 §11)的记录。
Generated by Claude Code