Skip to content

fix(runtime): 未知 auth 子路径给干净 404,不再外漏内部 TypeError (#5085) - #5774

Merged
baozhoutao merged 2 commits into
mainfrom
claude/issue-5085-auth-forward-unknown-subpath
Aug 6, 2026
Merged

fix(runtime): 未知 auth 子路径给干净 404,不再外漏内部 TypeError (#5085)#5774
baozhoutao merged 2 commits into
mainfrom
claude/issue-5085-auth-forward-unknown-subpath

Conversation

@baozhoutao

Copy link
Copy Markdown
Contributor

Fixes #5085

前提复核(基于最新 origin/main 889ae47,非 issue 基线 29c6c9d)

issue 的判读成立,且缺陷在最新 main 上仍然存在。不是静态推断——在 LiteKernel + HonoServerPlugin + createDispatcherPlugin 的真实 hono boot 上打了一次探针,拿回的响应体与 issue 贴的逐字一致:

PROBE_STATUS=500
PROBE_BODY={"success":false,"error":{"code":"INTERNAL_ERROR","message":"request.headers.get is not a function","httpStatus":500}}

责任面确认落在 packages/runtime,不涉 plugin-auth(边界规则不触发):plugin-auth/auth/* 通配转发的是 c.req.raw(真实 Fetch Request),packages/adapters/hono 两处也都是 c.req.raw。全仓唯一把非 Fetch 请求交给 better-auth 的产出方,是 dispatcher-plugin.ts 的那一条遗留显式路由。

成因

IHttpServer 交给 handler 的是适配器内部的 IHttpRequest,其 headersHonoHttpServer.runHandlerc.req.header() 造的普通对象,不是 HeaderscreateDispatcherPlugin 过去挂了一条 POST ${prefix}/auth/login(注释自称「legacy explicit … retained for self-hosted clients」),把这个对象原样喂进 dispatcher.handleAuth(…, { request: req });/auth 域再把 context.request 整个交给 IAuthService.handleRequest(request: Request),而 better-auth 的 fetch 风格 handler 第一件事就是 request.headers.get(…)TypeError → 落到 errorResponseBase,那里只在 looksLikeInternalErrorLeak 命中时消毒,而该启发式只认 SQL/driver dump,对 TypeError 一言不发,于是原文出体。

这条路由对任何调用方都不可能工作:/login 根本不是 better-auth 端点(既不在 plugin-auth/src/auth-route-ledger.ts 里,content/docs/api/plugin-endpoints.mdx 也早就白纸黑字写着 "There is no /auth/login route"),而且 /auth 域自 #4113完全不按子路径路由。它相对于原生应用上的 /auth/* 通配唯一多出来的东西,就是一个 500 —— 通配本来会让 better-auth 用真实 Request 给出它自己的干净 404。

修法(两半,均在 packages/runtime)

① 产出方:删掉那条遗留路由。 按 Prime Directive #12,修产出方而非在消费方加转换:在这里补一个 Fetch Request 转换属于消费方宽容,而且换来的只是一个更贵的 404。删后未知 auth 子路径与其它所有路径一样落给命名空间属主。原位置留下一段 DELIBERATELY NOT MOUNTED 注释,记下测量证据与理由,免得下次有人再把它加回来。

② 出口:handleAuthRequesthandleRequest 的 throw 无条件扣留原文。 auth service 自己拥有路由,所以它抛出的东西在这一层是不可归因的:本域没看过子路径、没解析过 body,分不清是调用方写错还是 handler 有 bug。#5462 已经记过「关键词启发式的一次否定不是安全的证据」,#5489mapDataError 的终端分支(UNCLASSIFIED_FAULT)写下的正是同一条纪律,并且点名 handler TypeError 就是落到那里的形状。现在按 #5437/#5464 的惯例无条件扣留:500 + 目录里 standardErrorCodeForHttpStatus(500)INTERNAL_ERROR + INTERNAL_ERROR_MESSAGE,原始错误交服务端日志。

诚实路径零代价:better-auth 自己的失败是返回 Response 而不是抛(这也是 AuthPlugin 通配要主动记录 >=500 响应 的原因),所以真实的 401/403/404/422 依旧原样返回。

反向验证(方向为,事先预测,三段分开测量)

状态 POST /api/v1/auth/login 的线上答复
两半都撤(= 修前 origin/main) 500 {"code":"INTERNAL_ERROR","message":"request.headers.get is not a function"} ← 与 issue 逐字吻合
只留 ②(路由还在) 500 {"code":"INTERNAL_ERROR","message":"Internal server error"} ← 泄漏没了,但错误类别仍然错
两半都在(本 PR) 404

中间那一档正是「两半都要」的实证:光消毒不删路由,拿到的还是一个语义错误的 500。

把遗留路由加回去,新增的三条断言立刻转红,报错正是缺陷本身:

FAIL src/auth-unknown-subpath.hono.integration.test.ts > answers an unknown auth sub-path with better-auth's own 404
AssertionError: expected 500 to be 404
FAIL src/auth-unknown-subpath.hono.integration.test.ts > does not resurrect /auth/login for any verb
AssertionError: expected 405 to be 404
FAIL src/auth-forward-fault-sanitization.test.ts > mounts nothing under ${prefix}/auth
AssertionError: expected [ …(52) ] to not include 'POST /api/v1/auth/login'

(GET 那条拿到 405 而不是 404,是被恢复的 POST mount 的错方法回收桶——这本身也是遗留路由多出来的一份噪声。)

测试

本地实测:pnpm --filter @objectstack/runtime test101 files / 1456 tests 全绿;typechecktsc --noEmit 无输出;@objectstack/hono 73 tests 全绿;check:route-envelope / check:wildcard-fallthrough / check:error-code-casing / check:adr-anchors / check:nul-bytes 全绿。

范围外发现

已作为 #5772 单独立项(observation-class,finding 标签,未认领):packages/plugins/plugin-auth/IMPLEMENTATION_SUMMARY.md 的 "API Routes Registered" 一节与 packages/client/CLIENT_SERVER_INTEGRATION_TESTS.md 的 MSW 示例,仍把 /api/v1/auth/login(以及 /register/logout/session)列为已注册路由 —— 四条一条都不存在。属文档车道,本 PR 不碰。


🤖 Generated with Claude Code

https://claude.ai/code/session_01DWUR56YsttL5sTF72Q75TQ


Generated by Claude Code

claude added 2 commits August 6, 2026 04:22
…aked internal TypeError

The dispatcher plugin mounted one legacy explicit route, POST ${prefix}/auth/login,
which handed better-auth the adapter's INTERNAL IHttpRequest (headers is a plain
object, not Headers). better-auth's fetch-style handler opens with
request.headers.get(...), so the route answered HTTP 500 with the raw
'request.headers.get is not a function' in the response body. /login is not a
better-auth endpoint at all, so the mount could never work for any caller.

- dispatcher-plugin.ts: delete the legacy route. Unknown auth sub-paths now fall
  to the /auth/* wildcard the namespace owner mounts on the raw Hono app, which
  forwards a real Fetch Request and yields better-auth's own clean 404.
- domains/auth.ts: a throw out of IAuthService.handleRequest is unattributable
  here, so its message is withheld unconditionally (#5437/#5464/#5489 discipline)
  — 500 INTERNAL_ERROR with the original error on the server log.

Refs #5085

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DWUR56YsttL5sTF72Q75TQ
…angeset

- auth-forward-fault-sanitization.test.ts: the dispatcher plugin mounts NO route
  under ${prefix}/auth (the specific legacy mount AND the general invariant),
  and a throw out of IAuthService.handleRequest is a sanitised 500 whose body
  carries none of the thrown text, with the original error on the server log.
  Positive controls: a better-auth Response passes through untouched (same
  object), its own 401 body is not sanitised, and an empty auth slot still 501s.
- auth-unknown-subpath.hono.integration.test.ts: a real hono boot, with a fake
  auth service that is better-auth-SHAPED (reads request.headers.get + new
  URL(request.url) first thing, so an internal request object explodes here the
  way it did in production). POST /auth/login → 404, sign-in/email → 200 with
  its set-cookie.

Refs #5085

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DWUR56YsttL5sTF72Q75TQ
@vercel

vercel Bot commented Aug 6, 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 Aug 6, 2026 4:42am

Request Review

@github-actions github-actions Bot added the size/l label Aug 6, 2026
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/runtime.

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

  • content/docs/api/client-sdk.mdx (via packages/runtime)
  • 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/metadata-lifecycle.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/index.mdx (via @objectstack/runtime)
  • content/docs/deployment/production-readiness.mdx (via @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/runtime)
  • content/docs/kernel/cluster.mdx (via @objectstack/runtime)
  • content/docs/permissions/authentication.mdx (via @objectstack/runtime)
  • content/docs/permissions/authorization.mdx (via packages/runtime)
  • content/docs/plugins/packages.mdx (via @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/releases/implementation-status.mdx (via @objectstack/runtime)
  • content/docs/releases/v17.mdx (via @objectstack/runtime)

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.

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Aug 6, 2026
@baozhoutao
baozhoutao marked this pull request as ready for review August 6, 2026 04:50
@baozhoutao
baozhoutao added this pull request to the merge queue Aug 6, 2026
Merged via the queue into main with commit 9f747ee Aug 6, 2026
24 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-5085-auth-forward-unknown-subpath branch August 6, 2026 04:57
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

2 participants