做 #5423(4xx 直通改截断)时扫到。不在那个 PR 范围内(那单只动 4xx 一侧,且刻意不放宽 5xx),按 Prime Directive #10 单独记在这里,unassigned。
成因
packages/rest/src/rest-server.ts 的 resolveErrorResponse(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_metadata、relation "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
做 #5423(4xx 直通改截断)时扫到。不在那个 PR 范围内(那单只动 4xx 一侧,且刻意不放宽 5xx),按 Prime Directive #10 单独记在这里,unassigned。
成因
packages/rest/src/rest-server.ts的resolveErrorResponse(sendError的取值端)直通区间是 400–599,不是 4xx:命中即原样返回
error.message(#5423 之后:4xx 截断、5xx 短于 500 仍逐字直通),完全不经过mapDataError—— 也就不经过isSqlLeak/looksLikeInternalErrorLeak/Internal data error那一整套消毒。对照:
mapDataError自己的同族分支把区间刻意限死在 4xx,注释写得很明白:两处对同一件事给了相反的取向。走
sendError的路由(metadata / UI / discovery 等)拿到的是宽区间那一版。活体产出方(不是休眠代码)
packages/metadata-protocol/src/protocol.ts有两处把原始驱动报错插值进 message 后带status: 500抛出: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 = 500Failed to delete customization overlay: ${err.message},status = 500${dbError.message}就是驱动原文。一条典型的 SQLite/Postgres 报错(SQLITE_ERROR: no such table: sys_metadata、relation "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 逐字保持不变,并有一条断言把这个刻意的不对称钉住。可选方向(不代裁决)
mapDataError的消毒路径 —— 与mapDataError注释里已记录的取向一致,最省解释;代价是现在能看到具体 500 原因的客户端会改看通用文案(日志侧不受影响)。OVERLAY_PERSISTENCE_FAILED这类自撰的、确实该让调用方看到的 500 正文,只拦驱动原文;代价是启发式的判准要担更多责任。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