Skip to content

test(security): pin static-readonly server enforcement end-to-end + fix its declared contract (#3003) - #3015

Merged
os-zhuang merged 1 commit into
mainfrom
claude/wizardly-fermi-kriftd
Jul 16, 2026
Merged

test(security): pin static-readonly server enforcement end-to-end + fix its declared contract (#3003)#3015
os-zhuang merged 1 commit into
mainfrom
claude/wizardly-fermi-kriftd

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

背景(#3003)

#3003 用真机复现坐实了 #2948 修复前的缺口:仅靠静态 readonly: true "保护"的审批/状态/金额字段,可以在同一登录会话里用一次直接 REST PATCH 伪造写入,在已发布的 15.0.0 上实现草稿自批(RECORD_LOCKED 只拦活动态审批流,草稿从未入流)。

引擎侧修复(stripReadonlyFields,#2957)已在 main 上:非 system 上下文的 UPDATE(单条 + 多行路径)会把调用方提交的静态 readonly 字段静默剥离(HTTP 200、保留原值),与 readonlyWhen 对称,insert 豁免——但该修复在 15.0.0 发布之后才落地,尚未随版本发出。本 PR 不改运行时行为,补的是防止该契约再次静默回退或被误读的全部配套:

变更

  • 端到端 dogfood 证明showcase-static-readonly.dogfood.test.ts(@proof: readonly-static-write):在真实 showcase 应用上走真 HTTP——非管理员 owner 直接 PATCH 伪造 lead_score → 200 且持久值不变、同载荷的可编辑字段正常落库;纯伪造载荷等价 no-op;INSERT 仍可播种该列(与 readonlyWhen 对称的文档化豁免)。
  • authz 一致性矩阵新增 readonly-static-write 行并列入 HIGH_RISK——证明文件成为强制项,删除即 CI 失败。
  • ADR-0054 proof-registry 新增 bound class,绑定 field/readonly:liveness 台账中该属性标 live 必须携带上述证明。
  • 契约文本修正:FieldSchema.readonly 的描述原文是 "Read-only in UI"——正是 安全:静态字段 readonly: true 仅 UI 层生效、服务端不强制 → 审批/状态/金额字段可被直接 PATCH 绕过写入(FLS 缺口) #3003 所记录的误读根源。现已把 Zod 描述、字段 liveness 台账(readonly + readonlyWhen)与手写文档(fields.mdxvalidation-rules.mdx)统一改为陈述服务端剥离语义(非 system UPDATE 剥离、insert 豁免)。
  • changeset(@objectstack/spec patch,描述性修正)。

验证

  • 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

…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
@vercel

vercelBot commented Jul 16, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
specBuildingBuildingPreview, CommentJul 16, 2026 4:52am

Request Review

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation protocol:data tests tooling size/m labels Jul 16, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/dogfood, @objectstack/spec.

98 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx(via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx(via @objectstack/spec)
  • content/docs/ai/skills.mdx(via @objectstack/spec)
  • content/docs/api/client-sdk.mdx(via @objectstack/spec)
  • content/docs/api/environment-routing.mdx(via @objectstack/spec)
  • content/docs/api/error-catalog.mdx(via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx(via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx(via @objectstack/spec)
  • content/docs/api/index.mdx(via @objectstack/spec)
  • content/docs/automation/approvals.mdx(via packages/spec)
  • content/docs/automation/flows.mdx(via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx(via packages/spec)
  • content/docs/automation/hooks.mdx(via @objectstack/spec)
  • content/docs/automation/index.mdx(via @objectstack/spec)
  • content/docs/automation/webhooks.mdx(via @objectstack/spec)
  • content/docs/automation/workflows.mdx(via @objectstack/spec)
  • content/docs/concepts/architecture.mdx(via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx(via packages/spec)
  • content/docs/concepts/index.mdx(via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx(via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx(via packages/spec)
  • content/docs/concepts/north-star.mdx(via packages/spec)
  • content/docs/data-modeling/analytics.mdx(via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx(via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx(via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx(via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx(via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx(via @objectstack/spec)
  • content/docs/data-modeling/index.mdx(via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx(via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx(via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx(via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx(via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx(via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx(via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx(via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx(via @objectstack/spec)
  • content/docs/getting-started/cli.mdx(via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx(via @objectstack/spec)
  • content/docs/getting-started/examples.mdx(via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx(via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx(via @objectstack/spec)
  • content/docs/getting-started/validating-metadata.mdx(via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx(via @objectstack/spec)
  • content/docs/kernel/cluster.mdx(via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx(via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx(via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx(via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx(via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx(via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx(via packages/spec)
  • content/docs/kernel/index.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/email-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx(via packages/spec)
  • content/docs/kernel/services-checklist.mdx(via @objectstack/spec)
  • content/docs/permissions/authorization.mdx(via packages/dogfood, @objectstack/spec)
  • content/docs/permissions/delegated-administration.mdx(via packages/dogfood)
  • content/docs/permissions/permission-sets.mdx(via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx(via @objectstack/spec)
  • content/docs/permissions/positions.mdx(via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx(via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx(via @objectstack/spec)
  • content/docs/plugins/development.mdx(via @objectstack/spec)
  • content/docs/plugins/index.mdx(via @objectstack/spec)
  • content/docs/plugins/packages.mdx(via @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx(via @objectstack/spec)
  • content/docs/protocol/diagram.mdx(via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/runtime-capabilities.mdx(via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx(via packages/spec)
  • content/docs/protocol/objectql/query-syntax.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx(via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx(via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx(via @objectstack/spec)
  • content/docs/releases/index.mdx(via @objectstack/spec)
  • content/docs/releases/v12.mdx(via @objectstack/spec)
  • content/docs/releases/v13.mdx(via @objectstack/spec)
  • content/docs/releases/v9.mdx(via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx(via @objectstack/spec)
  • content/docs/ui/dashboards.mdx(via @objectstack/spec)
  • content/docs/ui/forms.mdx(via @objectstack/spec)
  • content/docs/ui/index.mdx(via @objectstack/spec)
  • content/docs/ui/setup-app.mdx(via @objectstack/spec)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@os-zhuang
os-zhuang marked this pull request as ready for review July 16, 2026 05:13
@os-zhuang
os-zhuang merged commit 13c0a12 into mainJul 16, 2026
16 of 17 checks passed
@os-zhuang
os-zhuang deleted the claude/wizardly-fermi-kriftd branch July 16, 2026 05:13
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationprotocol:datasize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

安全:静态字段 readonly: true 仅 UI 层生效、服务端不强制 → 审批/状态/金额字段可被直接 PATCH 绕过写入(FLS 缺口)

2 participants

@os-zhuang@claude