test(security): pin static-readonly server enforcement end-to-end + fix its declared contract (#3003)#3015
Merged
Merged
Conversation
…ix its declared contract (#3003) #3003 reproduced the pre-#2948 gap in production terms: approval/status/amount columns "protected" only by static `readonly: true` were forged with one direct same-session REST PATCH on a draft record, self-approving a 4-stage approval on the released 15.0.0 (RECORD_LOCKED only guards pending flows, and the strip that closes this — stripReadonlyFields, #2957 — landed after that release cut). The engine fix is already on main; what was still missing is everything that keeps the contract from silently regressing or being misread again: - dogfood proof (showcase-static-readonly.dogfood.test.ts, @proof: readonly-static-write): over real HTTP on the showcase app, a non-admin owner's PATCH forging `lead_score` returns 200 with the persisted value kept while sibling editable fields land, an all-forged payload is a no-op, and INSERT may still seed the column (the documented readonlyWhen-symmetric exemption). - authz conformance row `readonly-static-write` (+ HIGH_RISK) so the proof is mandatory — deleting it fails CI, not review. - ADR-0054 proof-registry class bound to `field/readonly`, so the liveness ledger's `live` classification must carry the proof. - contract text: FieldSchema.readonly described itself as "Read-only in UI" — exactly the misreading #3003 documents. The Zod description, the field liveness ledger (readonly + readonlyWhen), and the hand-written field docs now state the server-side strip (non-system UPDATE, insert exempt). Closes #3003. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TKR6MTrGunV4p4AUfbKMDU
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
📓 Docs Drift CheckThis PR changes 2 package(s): 98 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
os-zhuang
marked this pull request as ready for review
July 16, 2026 05:13
This was referenced Jul 16, 2026
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.
背景(#3003)
#3003 用真机复现坐实了 #2948 修复前的缺口:仅靠静态
readonly: true"保护"的审批/状态/金额字段,可以在同一登录会话里用一次直接 RESTPATCH伪造写入,在已发布的 15.0.0 上实现草稿自批(RECORD_LOCKED只拦活动态审批流,草稿从未入流)。引擎侧修复(
stripReadonlyFields,#2957)已在 main 上:非 system 上下文的 UPDATE(单条 + 多行路径)会把调用方提交的静态 readonly 字段静默剥离(HTTP 200、保留原值),与readonlyWhen对称,insert 豁免——但该修复在 15.0.0 发布之后才落地,尚未随版本发出。本 PR 不改运行时行为,补的是防止该契约再次静默回退或被误读的全部配套:变更
showcase-static-readonly.dogfood.test.ts(@proof: readonly-static-write):在真实 showcase 应用上走真 HTTP——非管理员 owner 直接 PATCH 伪造lead_score→ 200 且持久值不变、同载荷的可编辑字段正常落库;纯伪造载荷等价 no-op;INSERT 仍可播种该列(与readonlyWhen对称的文档化豁免)。readonly-static-write行并列入 HIGH_RISK——证明文件成为强制项,删除即 CI 失败。field/readonly:liveness 台账中该属性标live必须携带上述证明。FieldSchema.readonly的描述原文是 "Read-only in UI"——正是 安全:静态字段 readonly: true 仅 UI 层生效、服务端不强制 → 审批/状态/金额字段可被直接 PATCH 绕过写入(FLS 缺口) #3003 所记录的误读根源。现已把 Zod 描述、字段 liveness 台账(readonly+readonlyWhen)与手写文档(fields.mdx、validation-rules.mdx)统一改为陈述服务端剥离语义(非 system UPDATE 剥离、insert 豁免)。@objectstack/specpatch,描述性修正)。验证
packages/spec:253 个测试文件 / 6856 个用例全绿(含 proof-registry 绑定更新)。packages/dogfood:50 个文件通过 / 1 个既有 skip(271 通过 / 3 skip),含新证明与矩阵 ledger 校验。pnpm check:liveness:✓,新 proof tag 已注册、非孤儿。pnpm build:70/70 成功。Closes #3003.
🤖 Generated with Claude Code
https://claude.ai/code/session_01TKR6MTrGunV4p4AUfbKMDU
Generated by Claude Code