Repository navigation
Description
做 #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
Activity
baozhoutao commentedon Aug 5, 2026
分诊(cli 车道 PM,
session_016FNvXhtSdnEGEfLEsMmvxh,2026-08-05):入队pm:queue,domain:cli(落点packages/rest,同 #5423 先例)。判级:真缺陷且安全相关 —— 5xx 原始驱动报错(含表名/约束负载)绕过全部消毒直达客户端,有活体产出方;且mapDataError注释已把「internal/SQL details never reach the client verbatim」写成成文取向,本单是恢复成文不变量,PM 直接裁定不上交。方向裁定(留否决窗口):取 A(直通区间收窄到 4xx,5xx 落回消毒路径)+ pin;B 否决(把责任压给关键词启发式,判准必然漏);C(产出方不插值原始 db 报错)是正确的长期补充但不在本单 —— 产出方清单跨
metadata-protocol(engine)与plugin-auth(identity),按跨车道协议由各自车道依本单清单另行立项,本单落地后在此留转记评论。两轴:A 让「客户端永远拿不到未消毒 5xx」成为结构性保证(收紧消毒面而非依赖每个产出方自律);运维照旧看日志,自撰 500 正文(如OVERLAY_PERSISTENCE_FAILED)的客户端可读性损失是已记录的接受代价 —— 维护者若不接受此代价可否决改 B。执行细节:补正文「未验证部分」(真实造一次sys_metadata写失败走通;动态赋值 status 的产出路径抽查);pin 双向(5xx 原文不达客户端 + 4xx 截断行为不回归)。⛔ 串行约束(step 3 已知陷阱):与 PR #5436(#5423)同文件
rest-server.ts,须待其 MERGED 后基于新 main 派发。Generated by Claude Code
baozhoutao commentedon Aug 5, 2026
认领:PM 循环第 5 轮(cli 车道)
会话:
session_016FNvXhtSdnEGEfLEsMmvxh分支:
claude/issue-5437-rest-5xx-sanitizeWorktree:
objectstack-issue-5437域:
domain:cli文件面:
packages/rest/src/rest-server.ts+packages/rest测试(串行约束已解除:PR #5436 已 MERGED39396bd;与在飞 #5417packages/lint、#5443examples/**不相交)执行口径沿 11:53Z 分诊裁定:方向 A(直通区间收窄到 4xx,5xx 落回
mapDataError消毒路径)+ 双向 pin;补齐正文「未验证部分」(真实造一次sys_metadata写失败走通;动态 status 产出路径抽查);产出方清理(C)不在本单,留 engine/identity 依清单立项。Generated by Claude Code
baozhoutao commentedon Aug 5, 2026
验收(cli 车道 PM,
session_016FNvXhtSdnEGEfLEsMmvxh):ACCEPT → PR #5464。公开认账一条 PM 机制误裁:分诊裁定的字面机制(「5xx 落回
mapDataError消毒路径」)被 dev 实现前实测证伪 —— 该路径按消息文本推导状态,overlay 500 会误判成 404 OBJECT_NOT_FOUND(且落进isExpectedDataStatus连日志都没有)、501 变 404、关键词不命中的 5xx 换个 4xx 壳照样漏原文;证伪它的恰是分诊自己要求的 pin ③(501 同验)。dev 保住裁定意图(5xx 原文永不出客户端)换用正确机制:直通分支内无条件扣正文、保留产出方 status + code,logWithheldServerFault补齐 502/503 静默口子。这是裁定该有的执行方式 —— 意图约束行为,机制服从实测。open_question 答复(PM 直接拍,不上交):取 A(维持现状)。501 自撰正文被扣属分诊已写明的「接受代价」条款同类延伸;B(clientSafeMessage 白名单)按你自己的两轴分析否决 —— 声明式豁免把判断权交回每个产出方,正是消费端宽容的变体;需要可读能力协商文案时走 C(产出方把安全话与驱动原文分开写),该路径已在分诊注记中留给 engine/identity 车道,你的 501 案例已追加为 C 立项时的素材。维护者否决窗口照旧。
SQLITE_ERROR措辞,动态status赋值形状的探测方法记录在测试头;saveMetaItem未能 live 的原因如实写明未假装。looksLikeUnknownObject把基础设施故障误报 404 且零日志)已确认并分诊。CI 核后转 ready 挂 auto-merge,跟到 MERGED。
Generated by Claude Code
fix(rest): a declared 5xx no longer ships its own message to the clie…
fix(rest): a missing relation is an unknown OBJECT only when it is th…
fix(metadata-protocol): an unreadable sys_metadata is an outage, not …
fix(runtime): 未知 auth 子路径给干净 404,不再外漏内部 TypeError (objectstack-ai#5085)…
test(rest): keep #5437's log guarantee measuring after the producer w…
fix(rest): type the REST door's author-side error responder to the cl…
fix(rest): a declared 5xx status survives mapDataError on the CRUD da…
fix(rest): the direct-mount package door withholds a leaky 5xx message (
fix(metadata-protocol): stop interpolating raw driver text into clien…
fix(rest): type the REST door's author-side error responder to the cl…
fix(rest): serve a sandboxed hook's own sentence on the bulk write ro…
fix(rest): the record-share family carries a producer-marked `userMes…
fix(rest): the record-share family carries a producer-marked `userMes…
feat(rest): a declared OS_REST_LOG fault-log level seam, shipped defa…