fix(rest): 零声明字段的公开表单拒收提交,而不是接受调用方发的任意键 (#6920) - #7013
Merged
os-project-manager merged 4 commits intoAug 9, 2026
Conversation
…tion-less-form-refusal
…tion-less-form-refusal
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 12 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
os-project-manager
marked this pull request as ready for review
August 9, 2026 09:51
os-project-manager
deleted the
claude/issue-6920-section-less-form-refusal
branch
August 9, 2026 10:05
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #6920
实现维护者裁决
5229989845(对称拒收):声明了零字段的公开表单不接受任何字段,与`` #6601 的读侧同一条规则。复现(改动前,
origin/main真实注册的 handler,匿名)sections: []的公开表单,发业务键 + 全套FORGED锚点:与卡上的测量逐字一致。注意这不是「对象的每个字段」——是调用方发的每一个键,减去 #3022 的锚点和三个原型键。
not_even_a_field根本不是该对象的字段。匿名访客因此可以设status、内部分层、公式列……凡是该对象有的都行。进入这个状态不需要任何刁钻配置:作者先建表单、后接 sections,就是普通的编辑中间态。改动
packages/rest/src/rest-server.ts(仅 submit 路由段)去掉过滤器里的allowedFields.size === 0 ||限支,并在收集完allowedFields之后前置一道拒收:判据是「声明」,不是 body。 空 body 同样被拒——按 body 判会让空提交正好穿过去插一条空记录,那正是本卡要消灭的形态。三种作者写法(
sections: []、有 section 但不声明字段、完全省略sections)一视同仁。为什么是拒收而不是静默丢弃。 丢弃会保留
201、不改变任何 wire 状态,但会吞掉调用方以为写进去的数据——访客被告知工单已提交,库里存了一行空记录,正是 AGENTS.md 的warn/error规则点名的那类静默。而且响亮是唯一能传到作者耳朵里的答案,只有作者能修。错误码为什么选
VALIDATION_ERROR按 #6307
readSingleQueryValue的同一条推理(见package-routes.ts的 “Where this module's error codes came from”):通用条件复用标准目录,而不是为它铸一个已注册的同义词。这里的条件是通用的 400 ——VALIDATION_ERROR是 ADR-0112 目录里的 generic validation failure,也正是HttpStatusErrorCodeMap[400]给裸 400 的命名。没有新增FORM_*码,ERROR_CODE_LEDGER不动。消息点名空声明、给出作者的修法(把字段接进 sections),且不含对象名、字段名或 slug —— 这条回复是全互联网可读的。
重判 #3022 的钉子(裁决明确要求)
public-form-routes.test.ts里zero declared sections: business fields fall through, anchors do NOT把 fall-through 钉为「预期行为」,并用一个被接受的未声明status来证明它。该用例已删除,原处留下注释写明依据裁决5229989845及为什么它是错的不变量。#3022 的锚点那一半原样保留,并且加强了:
declared-field whitelist…未动:表单甚至误声明owner_id,整套FORGED仍被丢弃。anchors alone on a DECLARED form:body 只有锚点、走接受路径,data为{}。a __proto__ body key…从buildServer([])重拼到声明式表单。原型污染这条性质从来与零 sections 无关——它此前只是搭了 fall-through 的便车。挪到声明路径后是更强的钉子,因为它守护的赋值循环真的会跑。expect(createData).toHaveBeenCalledTimes(0)接手。反向验证(先写预测,后跑)
预测:还原原码(删拒收 + 恢复
allowedFields.size === 0 ||)→ 恰好 7 条零 sections 用例转红;两条NO-REGRESSION与全部 #3022/#6601 用例双向常绿。实测:
Tests 7 failed | 14 passed (21),逐条吻合。方向是标准的 red,无反转。未写入已单独钉住,不止钉状态码:
expect(createData, 'a refusal must not reach the data plane at all').toHaveBeenCalledTimes(0)—— 一个「拒收了但仍插空行」的实现比原缺陷更糟。破坏面
这是把一条已发布的成功路径变成拒收,故 changeset 为
minor。今天靠零 sections 公开表单收集提交的部署会开始收到 400,修法是把字段声明进sections。该表单的读侧自 #6601 起本就发布fields: {}(渲染不出来),两个面现在执行同一条规则。已声明 sections 的表单完全不受影响——那条路径从不经过被删的限支。验证
pnpm --filter @objectstack/rest test→ 73 files / 1151 tests passed(改前 main 为 1132)pnpm --filter @objectstack/rest typecheck→ cleaneslint两个改动文件 → cleancheck:route-envelope/check:error-code-casing/check:empty-changeset/check:nul-bytes/check:spec-parsed-alias/check:doc-authoring/check:wildcard-fallthrough全 PASS范围
rest-server.ts只动 submit 路由的字段过滤段(外加GET /forms/:slug里一处指向被删限支的注释交叉引用,已失效)。#6599 的/meta/_drafts段未触碰,也未改动任何共用测试文件。content/docs/ui/forms.mdx加了两行(一条规则说明 + 错误码表一行):该表就是这条路由的对外契约说明,新增错误码不加行会让文档在落地当刻即错。经 PM 裁定纳入(#6920 线程),且git diff origin/main自查确认 delta 仅为这两行。非content/docs/releases/。Generated by Claude Code