Skip to content

sendError 的显式状态直通覆盖 400–599,5xx 的原始驱动报错绕过全部泄漏启发式直达客户端(metadata-protocol 有活体产出方) #5437

Description

@baozhoutao

#5423(4xx 直通改截断)时扫到。不在那个 PR 范围内(那单只动 4xx 一侧,且刻意不放宽 5xx),按 Prime Directive #10 单独记在这里,unassigned。

成因

packages/rest/src/rest-server.tsresolveErrorResponse(sendError 的取值端)直通区间是 400–599,不是 4xx:

const passThroughStatus = error?.code !== 'OBJECT_NOT_FOUND'
    && typeof error?.status === 'number' && error.status >= 400 && error.status < 600;

命中即原样返回 error.message(#5423 之后:4xx 截断、5xx 短于 500 仍逐字直通),完全不经过 mapDataError —— 也就不经过 isSqlLeak / looksLikeInternalErrorLeak / Internal data error 那一整套消毒。

对照:mapDataError 自己的同族分支把区间刻意限死在 4xx,注释写得很明白:

deliberately limited to 4xx: 5xx messages keep going through the sanitizing heuristics below so internal/SQL details never reach the client verbatim.

两处对同一件事给了相反的取向。走 sendError 的路由(metadata / UI / discovery 等)拿到的是宽区间那一版。

活体产出方(不是休眠代码)

packages/metadata-protocol/src/protocol.ts 有两处把原始驱动报错插值进 message 后带 status: 500 抛出:

  • :7462 附近 —— Failed to persist customization overlay to sys_metadata: ${dbError.message}. In-memory registry was updated but will be lost on restart.,code: 'OVERLAY_PERSISTENCE_FAILED',status = 500
  • :9812 附近 —— Failed to delete customization overlay: ${err.message},status = 500

${dbError.message} 就是驱动原文。一条典型的 SQLite/Postgres 报错(SQLITE_ERROR: no such table: sys_metadatarelation "sys_metadata" does not exist、含表名的 unique 约束负载)远短于 500 字符,于是整条直达客户端。长度从来不是泄漏的代理指标 —— 这也正是 #5423 判 B 的理由之一,只不过在 5xx 这一侧,它是往放行的方向失效。

plugin-auth(:2182、admin-user-endpoints :477)、metadata-protocol :5582(status = 501)等也在这个区间内,未逐条清点消息内容。

为什么不在 #5423 里顺手改

#5423 的裁定是「不改 code/status 语义,不动 looksLikeInternalErrorLeak 启发式本身」,且那单的论点是面向调用方的 4xx 正文应当读得到。把 5xx 一并放宽是相反方向的改动;把 5xx 一并收紧则是本单这个决策,需要单独判 —— 收紧会改变现在能读到这些 500 正文的运维/客户端行为,不该作为搭车项落地。#5423 PR 里 5xx 逐字保持不变,并有一条断言把这个刻意的不对称钉住。

可选方向(不代裁决)

  • A:直通区间收窄到 4xx,5xx 落回 mapDataError 的消毒路径 —— 与 mapDataError 注释里已记录的取向一致,最省解释;代价是现在能看到具体 500 原因的客户端会改看通用文案(日志侧不受影响)。
  • B:5xx 仍直通但先过泄漏启发式,命中才替换 —— 保住 OVERLAY_PERSISTENCE_FAILED 这类自撰的、确实该让调用方看到的 500 正文,只拦驱动原文;代价是启发式的判准要担更多责任。
  • C:在产出方修 —— 契约优先的那一版:metadata-protocol 不要把 ${dbError.message} 插进面向客户端的 message,原文只进日志。治本,但要逐个产出方清点。

三者可组合(C + A 大概是长期最干净的)。

未验证的部分

按代码读 + 产出方 grep 得出,没有起 REST server 打真实请求造一次 sys_metadata 写失败。产出方清单只覆盖了 status: 字面量的 grep,动态赋值的路径没清点。

关联

#5423(本单的来源;4xx 一侧已修,5xx 刻意留下)、#4886(resolveErrorResponse 拆出的那一单)、#3867(消毒器)、#5085(同族:/auth/* 转发把内部 TypeError 外漏成 500)。


Generated by Claude Code

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions