Skip to content

test(plugin-auth): enumerate better-auth's route table — the /auth/** wildcard becomes 55 exact rows (#3656) - #3669

Merged
os-zhuang merged 1 commit into
mainfrom
claude/route-audit-tranche-3-service-wsxqaf
Jul 27, 2026
Merged

test(plugin-auth): enumerate better-auth's route table — the /auth/** wildcard becomes 55 exact rows (#3656)#3669
os-zhuang merged 1 commit into
mainfrom
claude/route-audit-tranche-3-service-wsxqaf

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

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 挂的:

rawApp.all(`${basePath}/*`, )   // auth-plugin.ts

所以没有逐路由的注册调用可捕获,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,完整清单 双向完全相等 版本升级新增了公开挂载的 auth 端点

这个不对称是决定,不是疏忽。 要求 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/** 吸收掉,新账本等于白加——这点我在代码注释里写明了。

做了变异测试:把某条账本行改名,守卫立刻失败并点名 ——

ledgered auth routes better-auth no longer serves — the SDK methods naming them are now wire-level 404s:
GET /api/v1/auth/get-session-RENAMED

验证

  • auth-route-ledger.conformance.test.ts 7 项通过;变异测试确认可失败
  • turbo run test --filter=@objectstack/plugin-auth --filter=@objectstack/client --filter=@objectstack/runtime —— 29/29
  • eslint --no-inline-configcheck-nul-bytescheck-doc-authoringcheck-role-word 均通过

无运行时改动:一份账本、一个守卫,以及账本头部与审计文档(新增 §11)的记录。


Generated by Claude Code

…*` 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
@vercel

vercel Bot commented Jul 27, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Jul 27, 2026 1:40pm

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling size/l labels Jul 27, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/client, @objectstack/plugin-auth, @objectstack/runtime.

31 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/skills-reference.mdx (via packages/client)
  • content/docs/api/client-sdk.mdx (via @objectstack/client, packages/runtime)
  • content/docs/api/data-flow.mdx (via @objectstack/client)
  • content/docs/api/environment-routing.mdx (via @objectstack/client)
  • content/docs/api/error-catalog.mdx (via @objectstack/client)
  • content/docs/api/index.mdx (via @objectstack/runtime)
  • content/docs/api/wire-format.mdx (via @objectstack/runtime)
  • content/docs/automation/hook-bodies.mdx (via @objectstack/runtime)
  • content/docs/concepts/north-star.mdx (via packages/runtime)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/runtime)
  • content/docs/deployment/cli.mdx (via @objectstack/plugin-auth)
  • content/docs/deployment/index.mdx (via @objectstack/runtime)
  • content/docs/deployment/production-readiness.mdx (via @objectstack/plugin-auth, @objectstack/runtime)
  • content/docs/deployment/single-project-mode.mdx (via @objectstack/runtime)
  • content/docs/deployment/vercel.mdx (via @objectstack/runtime)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/client, @objectstack/runtime)
  • content/docs/kernel/runtime-services/data-service.mdx (via packages/client)
  • content/docs/kernel/runtime-services/index.mdx (via packages/client)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/plugin-auth)
  • content/docs/permissions/authentication.mdx (via @objectstack/client, @objectstack/plugin-auth, @objectstack/runtime)
  • content/docs/permissions/authorization.mdx (via packages/runtime)
  • content/docs/permissions/sso.mdx (via @objectstack/plugin-auth)
  • content/docs/plugins/index.mdx (via @objectstack/plugin-auth)
  • content/docs/plugins/packages.mdx (via @objectstack/client, @objectstack/plugin-auth, @objectstack/runtime)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/runtime)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/runtime)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/runtime)
  • content/docs/protocol/kernel/realtime-protocol.mdx (via @objectstack/client)
  • content/docs/releases/implementation-status.mdx (via @objectstack/client, @objectstack/plugin-auth, @objectstack/runtime)
  • content/docs/releases/v16.mdx (via @objectstack/client)
  • content/docs/releases/v9.mdx (via @objectstack/plugin-auth)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@os-zhuang
os-zhuang marked this pull request as ready for review July 27, 2026 13:54
@os-zhuang
os-zhuang merged commit 984396b into main Jul 27, 2026
18 checks passed
@os-zhuang
os-zhuang deleted the claude/route-audit-tranche-3-service-wsxqaf branch July 27, 2026 13:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Enumerate the /auth/** dynamic route family — 54 SDK methods rest on a prefix claim, not a route

2 participants