Repository navigation
Description
现状
#5367(维护者 2026-08-06 裁决,方案 B)把 read-scope-sql.ts 的十处 fail-closed 拒收改成 READ_SCOPE_COMPILE_FAILED / 500,并让 POST /analytics/dataset/query withhold 任何声明了服务端故障(status >= 500 且带 code)的生产方 message。理由之一是这些 message 点名 RLS 策略的字段名与比较值:
[read-scope-sql] unsafe field identifier "secret_policy_field" — refusing to build read scope (fail-closed).
[read-scope-sql] unsupported operator "$regex" on "owner_email" (fail-closed).
[read-scope-sql] "approved_by_manager" has a nested/relation value which is not supported in a read scope (fail-closed).
这些内容来自管理员写的 sharing rule / permission set,由 security 服务编译后经 getReadScope 交给 analytics —— 调用方(租户)从来没写过它们,也不该能从错误体里读出来。
同一批拒收在姐妹面上没有被堵。 /analytics/query 经 dispatcher-plugin.errorResponseBase(packages/runtime/src/dispatcher-plugin.ts:445)退出,它的 message 判据是:
const message =
httpStatus >= 500 && looksLikeInternalErrorLeak(raw)
? INTERNAL_ERROR_MESSAGE
: raw || 'Internal Server Error';而 looksLikeInternalErrorLeak(packages/types/src/error-leak.ts:47)是对 SQL/驱动措辞 的启发式(sqlite_、SQLSTATE、以 SELECT/INSERT INTO/UPDATE/DELETE FROM 开头、constraint failed/unique constraint/foreign key)。实测把上面十一种 message 形态逐条喂进去:
ECHOED [read-scope-sql] unsafe field identifier "owner id" — refusing to buil…
ECHOED [read-scope-sql] unsafe alias identifier "crm opportunity" — refusing …
ECHOED [read-scope-sql] read scope must be a filter object (fail-closed).
ECHOED [read-scope-sql] "$and" requires an array (fail-closed).
ECHOED [read-scope-sql] unsupported top-level operator "$nor" (fail-closed).
ECHOED [read-scope-sql] bare array value for "region" — use { $in: [...] } (f…
ECHOED [read-scope-sql] "owner" has a nested/relation value which is not supp…
ECHOED [read-scope-sql] $in for "region" needs an array (fail-closed).
ECHOED [read-scope-sql] $nin for "region" needs an array (fail-closed).
ECHOED [read-scope-sql] $between for "amount" needs [min,max] (fail-closed).
ECHOED [read-scope-sql] unsupported operator "$regex" on "owner" (fail-closed…
11/11 全部 ECHOED —— 即 errorResponseBase 会把策略内容原样写进 error.message,状态码是 500(err.status 被采纳)但正文没有被收口。
read-scope-sql.ts 的 compileScopedFilterToSql 在 NativeSQLStrategy.applyReadScope 与 ObjectQLStrategy 的回显 SQL 两条路上都会跑,而这两条路都服务 /analytics/query,所以这条面是真实可达的,不是理论问题。
为什么没在 #5367 里一起做
#5367 的题目是 dataset 路由那串 message 正则名单的退休。这条泄漏在本单之前就存在、且在另一条路由上,把它一起改会把一次可见行为变更(该面 message 从可读变为收口)夹带进一个以「退休正则名单」为题的 PR。按 PD #10 单独立项。
PR #5808(#5367 的第二个 PR)里的规则是就地写在 rest-server.ts ③ 分支内的三行判断,故意没有抽成共享谓词 —— 只有一个消费者时提升就是推测性面。本单是第二个消费者出现的时刻。
建议方向(不预判)
- A(推荐形状):把 PR fix(analytics,rest)!: read-scope 拒收改判 READ_SCOPE_COMPILE_FAILED/500,路由的 message 正则名单完全退休 #5808 的判据提升成
@objectstack/types里与looksLikeInternalErrorLeak同居的一个小谓词 —— 例如declaresServerFault(err)(status >= 500且有非空code)—— 然后两个边界(rest-server.ts的 analytics dataset ③ 分支、dispatcher-plugin.errorResponseBase)都读它。error-leak.ts的文件头本来就写着「‘不要把内部细节发给客户端’是 HTTP 边界的属性,不是某个 router 的属性,所以谓词放在这里」,这正是它说的那种情况。 - B:只在
errorResponseBase内联同样的判断。省事,但两处实现一条规则,是 Envelope drift is not just service-storage: four more route modules emit bare bodies, two of them the pre-#3675{ error: '<string>' }#3843/analytics /query 未做 cube 存在性校验,未注册名直达驱动当表名;且错误路径原样回显驱动 SQL(#3770 同类,另一子系统) #3867 反复付过学费的形状。 - C:把
looksLikeInternalErrorLeak教会认[read-scope-sql]。不建议 —— 那是往回加措辞嗅探,与 analytics 的 filter 拒收到不了调用方:service 侧多数拒收没有 ADR-0112 信封,REST 面又用 message 正则嗅探,一律答 500 #5352/analytics dataset 路由的 message 正则兜底没有退休时间表:六族拒收仍靠措辞分类,改一个字就换一个 HTTP 码 #5367 刚清掉的机制同族。
无论选哪个都要注意不能把 withhold 放宽到「所有 5xx」:#5667 刻意保留了未声明 5xx 的可读性(自产故障 "no strategy can handle query …" 应当照旧可读),PR #5808 的用例两侧都钉了这一点,新实现要沿用。
验收建议
- 规则落地后,
/analytics/query面加端到端用例:read-scope 拒收 → 500,error.message为Internal server error,body 不含策略字段名,error.details.code为READ_SCOPE_COMPILE_FAILED(errorResponseBase已经会把err.code放进details,The dispatcher puts the HTTP status inerror.codeand parks the real code indetails— pinned in #3687, still unfixed #3842),完整原文在日志/errorReporter 侧。 - 反向:未声明 5xx 在该面仍然可读(fix(service-analytics,rest): analytics dimension 的源字段闸门 —— 不存在的 dimension 答 400 INVALID_FIELD,dataset 500 不再回显 SQL (#5520) #5667 分级不被吃掉)。
- changeset 写清这是可见行为变更(该面 message 由可读变收口),照 analytics 的 filter 拒收到不了调用方:service 侧多数拒收没有 ADR-0112 信封,REST 面又用 message 正则嗅探,一律答 500 #5352 changeset 里 "Observable behaviour change" 那一段的写法。
参考
packages/runtime/src/dispatcher-plugin.ts:445(errorResponseBase)、:468-:472(message 判据)、:480-:483(details.code)packages/types/src/error-leak.ts:47(looksLikeInternalErrorLeak)及其文件头关于「边界属性」的表述packages/services/service-analytics/src/read-scope-sql.ts(十处拒收 +readScopeCompileError)- PR fix(analytics,rest)!: read-scope 拒收改判 READ_SCOPE_COMPILE_FAILED/500,路由的 message 正则名单完全退休 #5808 / analytics dataset 路由的 message 正则兜底没有退休时间表:六族拒收仍靠措辞分类,改一个字就换一个 HTTP 码 #5367(dataset 路由侧的声明式 withhold 与裁决)、[17.0-rc2验收] analytics: 不存在的 dimension 500(泄漏 SQL / SQLITE_ERROR)而不是 400 指名字段 —— #4437 只给 measure 加了闸门,dimension 侧对称缺口仍在 #5520 / fix(service-analytics,rest): analytics dimension 的源字段闸门 —— 不存在的 dimension 答 400 INVALID_FIELD,dataset 500 不再回显 SQL (#5520) #5667(该 dataset 路由 5xx 收窄的两轮)、analytics /query 未做 cube 存在性校验,未注册名直达驱动当表名;且错误路径原样回显驱动 SQL(#3770 同类,另一子系统) #3867(启发式的出处)
Activity
os-zhuang commentedon Aug 6, 2026
存量裁决轮(维护者 2026-08-06 委托,session_01LeEfA7CFwbJb7JJmXm2KM3):裁定入队,并按落点锚定改标
domain:services→domain:cli(推荐方向 A 的落点在 packages/types + runtime,均属 cli 行)。前提经 origin/main 核实仍成立:dispatcher-plugin.ts:468-472 判据未变(仅httpStatus>=500 && looksLikeInternalErrorLeak),read-scope 拒收 message(含 RLS 策略字段名)11/11 原样回显——安全信息泄漏;declaresServerFault谓词全域零命中,尚未提升。分诊座位如认为锚定有误请改回。Generated by Claude Code
hotlong commentedon Aug 7, 2026
更正注记:本单正文里的
error.details.code是写错的落点(#6123)本单已完成关闭(PR #6122),这条只更正正文里一处会误导后续读者的说法,不改正文、不重开。出处与裁决见 #6123。
正文第三处同误说法——「验收建议」第 1 条:
以及「参考」小节的
`:480`-`:483`(`details.code`)。实际落点是
error.code,error.details根本不存在。 括号里那半句是对的——errorResponseBase确实把err.code放进details——但那只是进程内的暂存,不是线上位置。它交给共享构造器buildApiError(packages/runtime/src/error-envelope.ts:117)之后:splitSemanticCode取走code后rest为空,按其注释「undefinedrather than{}so a details payload that held nothing but the code disappears from the wire」返回details: undefined,于是最后一行的展开被跳过、details键整个消失。真实 500 body:{"success":false,"error":{"code":"READ_SCOPE_COMPILE_FAILED", "message":"Internal server error","httpStatus":500}}PR #6122 的实现本身没有跟着错。 它落地的用例
packages/runtime/src/analytics-query-read-scope-withhold.test.ts:218按实测断言在error.code上,并在上方写明了提升机制——所以受影响的只有本单正文这段文字,验收结论不变。为什么值得单独更正:
error.details.code在这条路上从来读不到,而按本仓惯例,一句指向不存在的键的说明,正是下一个 agent grep 不到之后自己发明??兜底的起点(#6123 的立单理由)。同族的另两处文字(#5367 的 changeset +
read-scope-sql.ts文件头)由 #6123 的 PR 一并更正。会话:
session_015a5qkLzpGXhLL2F5gvJ7dDGenerated by Claude Code
fix(rest): honour a hook refusal's declared `statusCode` at the `/api…
fix(rest): relay a producer-declared 5xx on `/analytics/dataset/query` (
docs(runtime): re-anchor the dead tracker citations in packages/runti…