Skip to content

feat(security)!: secure-by-default requireAuth flip + RBAC gates for transfer/restore/purge (#2561 P0) - #2562

Merged
os-zhuang merged 4 commits into
mainfrom
claude/production-launch-priorities-ps8hk3
Jul 4, 2026
Merged

feat(security)!: secure-by-default requireAuth flip + RBAC gates for transfer/restore/purge (#2561 P0)#2562
os-zhuang merged 4 commits into
mainfrom
claude/production-launch-priorities-ps8hk3

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

概述

落地 #2561(授权全生命周期缺口地图)framework 侧的两个 P0 上线阻断项:

  1. requireAuth 安全默认翻转(ADR-0056 D2 的 enforce 阶段)— BREAKING
  2. [security][立项位] M2 权限生命周期(undelete/purge)功能与 allowRestore/allowPurge RBAC 同批建设(evaluator 已 fail-closed,allowTransfer 已 enforced) #1883 — transfer/restore/purge 的 RBAC 门预映射

#2561 的第三个 P0(ADR-0086 objectui,越权数据暴露)在 objectstack-ai/objectui 仓库,本会话无权限,需另行处理(见 issue 勾选项)。

1. requireAuth 默认翻转(BREAKING)

变更
spec/api/rest-server.zod.tsrequireAuthdefault(false)default(true)
rest/rest-server.tsnormalizeConfig?? false?? true
rest/rest-api-plugin.tsboot warn 只在显式requireAuth:false 退出时触发;并修复了原实现读错嵌套路径(config.api.requireAuth 而非 config.api.api.requireAuth)导致即使开了 requireAuth 也误报 warn 的 bug
cli serve保留刻意的豁免:无 auth tier 的 stack(无人能登录)传显式 false + warn
verify harness去掉覆盖——整个 dogfood 套件在平台默认姿态下运行,showcase-anonymous-deny 从此证明的是默认值本身
一致性矩阵requireAuth-default-flip:experimentalenforced(带强制点 + dogfood proof)

翻转的前置条件已满足:公开表单经声明派生的 publicFormGrant 自授权(ADR-0056 Option A,已有 dogfood 证明),share-link 校验 token 后以系统上下文读取,控制面(/auth//health//discovery)豁免。

迁移(一行):刻意公开数据的部署需显式 api: { requireAuth: false }(changeset 已含 FROM→TO 说明)。

2. #1883 — 破坏性生命周期操作 RBAC 门

  • OPERATION_TO_PERMISSION 预映射 transfer→allowTransfer / restore→allowRestore / purge→allowPurge;modifyAllRecords 超级用户旁路覆盖该类操作(对齐 Salesforce "Modify All Data" 语义,管理员不会被锁死)。
  • 操作本身仍未进 ObjectQL(roadmap M2)——但门先于功能就位:操作一旦经安全中间件调度,无对应权限位即拒绝;未映射的破坏性操作继续 fail-closed(ADR-0049)。
  • spec 描述 [EXPERIMENTAL — not enforced][RBAC-gated; operation pending M2];liveness 账本补三条显式条目。
  • 单测:无位拒绝 / 有位放行 / 位不跨操作泄漏 / modifyAllRecords 旁路。

验证

  • 全量 turbo 测试套件绿(129 tasks;含 spec 6669、dogfood 181、rest 198、cli/runtime/client 全部)。
  • check:liveness 门通过(全部属性已分类)。
  • 浏览器实测(真实 showcase 服务器,Playwright 驱动 Chromium):
    • 匿名 GET/POST /api/v1/data/*401
    • 浏览器内登录 admin@objectos.ai 后同一 URL → 200
    • 匿名公开表单 GET /forms/contact-us → 200,submit201;同一匿名会话直打数据 API 仍 401(grant 不外溢)
  • 测试适配:REST/client 单测直接调度 handler、测的是路由机制而非认证门,按「刻意公开部署」显式 requireAuth:false 退出(认证门由 rest-auth-gate 单测 + anonymous-deny dogfood 覆盖)。

关联

🤖 Generated with Claude Code

https://claude.ai/code/session_014y5kiH3aPLWtRRRGcVrXcT


Generated by Claude Code

…transfer/restore/purge (#2561 P0)
Two production-launch blockers from the authorization gap map (#2561):
1. requireAuth default flip (ADR-0056 D2, enforce stage) — BREAKING
- spec: RestApiConfigSchema.requireAuth default(false) → default(true);
rest-server normalizeConfig mirrors (?? true). Anonymous /data/* is
now denied (401) unless the deployment explicitly opts out.
- rest-api-plugin: boot warning now fires only for the EXPLICIT
requireAuth:false opt-out, and reads the correct nested config path
(the flat read previously warned even when requireAuth was on).
- cli serve: keeps the deliberate carve-out — auth-less stacks get an
explicit false (nothing could authenticate against them), warned.
- verify harness: no override — every dogfood proof now runs on the
platform default; anonymous-deny + public-form survival prove the
flip (publicFormGrant was the pre-req that unblocked it).
- conformance matrix: requireAuth-default-flip experimental → enforced.
- REST/client unit tests that dispatch handlers anonymously opt out
explicitly (they test routing mechanics, not the auth gate).
2. #1883 — destructive lifecycle ops RBAC gate (pre-mapped)
- permission-evaluator: OPERATION_TO_PERMISSION now maps
transfer→allowTransfer, restore→allowRestore, purge→allowPurge;
modifyAllRecords super-user bypass covers the class. Ops still do
not exist in ObjectQL (M2) — but there is no ungated window when
they ship; unmapped destructive ops keep failing closed (ADR-0049).
- spec: bits re-described [EXPERIMENTAL — not enforced] →
[RBAC-gated; operation pending M2]; liveness ledger entries added.
- unit tests: deny-without-bit / allow-with-bit / no-cross-leak /
modifyAllRecords bypass.
Verified: full turbo test suite green (129 tasks); browser-driven
showcase run proves anonymous 401, authenticated 200, anonymous public
form 200/201 with the data API still closed to the same session.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014y5kiH3aPLWtRRRGcVrXcT
@vercel

vercelBot commented Jul 4, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
specReadyReadyPreview, CommentJul 4, 2026 8:05am

Request Review

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

github-actionsBot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 8 package(s): @objectstack/cli, @objectstack/client, @objectstack/dogfood, @objectstack/plugin-dev, @objectstack/plugin-security, @objectstack/rest, @objectstack/spec, @objectstack/verify.

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

  • content/docs/concepts/architecture.mdx(via @objectstack/spec)
  • content/docs/concepts/cloud-artifact-api.mdx(via packages/cli, packages/spec)
  • content/docs/concepts/cluster-semantics.mdx(via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx(via packages/spec)
  • content/docs/concepts/implementation-status.mdx(via @objectstack/cli, @objectstack/client, @objectstack/plugin-security, @objectstack/rest, @objectstack/spec, @objectstack/verify)
  • 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/concepts/packages.mdx(via @objectstack/cli, @objectstack/client, @objectstack/plugin-dev, @objectstack/plugin-security, @objectstack/rest, @objectstack/spec)
  • content/docs/concepts/setup-app.mdx(via @objectstack/spec)
  • content/docs/concepts/skills.mdx(via @objectstack/spec)
  • content/docs/concepts/webhook-delivery.mdx(via @objectstack/spec)
  • content/docs/getting-started/architecture.mdx(via @objectstack/spec)
  • content/docs/getting-started/cli.mdx(via @objectstack/cli, @objectstack/plugin-security, @objectstack/spec)
  • content/docs/getting-started/core-concepts.mdx(via @objectstack/spec)
  • content/docs/getting-started/examples.mdx(via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx(via @objectstack/cli, @objectstack/spec)
  • content/docs/guides/adding-a-metadata-type.mdx(via @objectstack/spec)
  • content/docs/guides/ai-capabilities.mdx(via @objectstack/spec)
  • content/docs/guides/airtable-dashboard-analysis.mdx(via @objectstack/spec)
  • content/docs/guides/analytics-datasets.mdx(via @objectstack/spec)
  • content/docs/guides/api-reference.mdx(via @objectstack/rest, @objectstack/spec)
  • content/docs/guides/authentication.mdx(via @objectstack/cli, @objectstack/client)
  • content/docs/guides/business-logic.mdx(via @objectstack/spec)
  • content/docs/guides/cheatsheets/backward-compatibility.mdx(via @objectstack/spec)
  • content/docs/guides/cheatsheets/error-catalog.mdx(via @objectstack/spec)
  • content/docs/guides/cheatsheets/field-type-gallery.mdx(via @objectstack/spec)
  • content/docs/guides/cheatsheets/field-validation-rules.mdx(via @objectstack/spec)
  • content/docs/guides/cheatsheets/permissions-matrix.mdx(via packages/plugins/plugin-security, @objectstack/spec)
  • content/docs/guides/cheatsheets/protocol-diagram.mdx(via packages/spec)
  • content/docs/guides/cheatsheets/query-cheat-sheet.mdx(via @objectstack/spec)
  • content/docs/guides/cheatsheets/quick-reference.mdx(via @objectstack/spec)
  • content/docs/guides/client-sdk.mdx(via @objectstack/cli, @objectstack/client, @objectstack/spec)
  • content/docs/guides/common-patterns.mdx(via @objectstack/spec)
  • content/docs/guides/contracts/auth-service.mdx(via packages/spec)
  • content/docs/guides/contracts/cache-service.mdx(via packages/spec)
  • content/docs/guides/contracts/data-engine.mdx(via @objectstack/spec)
  • content/docs/guides/contracts/index.mdx(via @objectstack/spec)
  • content/docs/guides/contracts/metadata-service.mdx(via packages/spec)
  • content/docs/guides/contracts/storage-service.mdx(via packages/spec)
  • content/docs/guides/data-modeling.mdx(via @objectstack/spec)
  • content/docs/guides/driver-configuration.mdx(via @objectstack/spec)
  • content/docs/guides/error-handling-client.mdx(via @objectstack/spec)
  • content/docs/guides/error-handling-server.mdx(via @objectstack/spec)
  • content/docs/guides/external-datasources.mdx(via @objectstack/spec)
  • content/docs/guides/formula.mdx(via @objectstack/spec)
  • content/docs/guides/hook-bodies.mdx(via packages/cli, packages/spec)
  • content/docs/guides/kernel-services.mdx(via @objectstack/spec)
  • content/docs/guides/metadata/dashboard.mdx(via @objectstack/plugin-security, @objectstack/spec)
  • content/docs/guides/metadata/field.mdx(via @objectstack/spec)
  • content/docs/guides/metadata/flow.mdx(via @objectstack/spec)
  • content/docs/guides/metadata/index.mdx(via @objectstack/spec)
  • content/docs/guides/metadata/object.mdx(via @objectstack/spec)
  • content/docs/guides/metadata/validation.mdx(via @objectstack/spec)
  • content/docs/guides/metadata/workflow.mdx(via @objectstack/spec)
  • content/docs/guides/packages.mdx(via @objectstack/cli, @objectstack/client, @objectstack/plugin-dev, @objectstack/plugin-security, @objectstack/rest, @objectstack/spec)
  • content/docs/guides/plugin-development.mdx(via @objectstack/spec)
  • content/docs/guides/plugins.mdx(via @objectstack/plugin-security, @objectstack/rest, @objectstack/spec)
  • content/docs/guides/project-scoping.mdx(via @objectstack/cli, @objectstack/client, @objectstack/spec)
  • content/docs/guides/public-forms.mdx(via @objectstack/spec)
  • content/docs/guides/runtime-services/data-service.mdx(via packages/cli, packages/client)
  • content/docs/guides/runtime-services/email-service.mdx(via packages/spec)
  • content/docs/guides/runtime-services/index.mdx(via packages/cli, packages/client, packages/spec)
  • content/docs/guides/runtime-services/queue-service.mdx(via packages/spec)
  • content/docs/guides/runtime-services/sharing-service.mdx(via packages/spec)
  • content/docs/guides/runtime-services/storage-service.mdx(via packages/spec)
  • content/docs/guides/security.mdx(via @objectstack/plugin-security, @objectstack/spec)
  • content/docs/guides/seed-data.mdx(via @objectstack/spec)
  • content/docs/guides/skills.mdx(via packages/cli, packages/client, @objectstack/spec)
  • content/docs/guides/solutions/approval-workflow.mdx(via packages/spec)
  • content/docs/guides/solutions/create-vs-edit-form.mdx(via @objectstack/spec)
  • content/docs/guides/solutions/data-automation-interface-access.mdx(via packages/plugins/plugin-security)
  • content/docs/guides/solutions/role-based-interfaces.mdx(via packages/plugins/plugin-security)
  • content/docs/guides/standards.mdx(via @objectstack/spec)
  • content/docs/guides/troubleshooting.mdx(via @objectstack/spec)
  • content/docs/guides/validating-metadata.mdx(via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx(via @objectstack/spec)
  • content/docs/protocol/objectos/config-resolution.mdx(via @objectstack/spec)
  • content/docs/protocol/objectos/i18n-standard.mdx(via @objectstack/spec)
  • content/docs/protocol/objectos/lifecycle.mdx(via @objectstack/spec)
  • content/docs/protocol/objectos/plugin-spec.mdx(via @objectstack/cli, @objectstack/spec)
  • content/docs/protocol/objectos/realtime-protocol.mdx(via @objectstack/cli, @objectstack/client)
  • content/docs/protocol/objectos/runtime-capabilities.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 packages/spec)
  • content/docs/protocol/objectui/record-alert.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx(via @objectstack/spec)
  • content/docs/releases/index.mdx(via @objectstack/spec)
  • content/docs/releases/v9.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.

…ssion docs
The permissions-matrix cheatsheet and ObjectQL security protocol page listed
allowTransfer/allowRestore/allowPurge without noting the operations do not
exist yet (roadmap M2) — per the declared≠enforced honesty rule, both now
carry the pending-M2 / gate-pre-mapped caveat (#1883). Also documents the
modifyAllRecords bypass extension to the lifecycle class.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014y5kiH3aPLWtRRRGcVrXcT
…in ADR-0066 future refinements
Two findings from the 2026-07 pre-launch authorization assessment that had
no ADR home:
- ⑧ runtime FLS is block-list + most-permissive union — undeclared fields
are visible by default and a field-level deny cannot be expressed until
the muting layer (⑦) covers field grants;
- ⑨ capability strings need an authoring/publish-gate lint when the D1
registry lands (typos currently fail closed but undiscoverably).
Companion engineering issue (not ADR material): #2565 — surface swallowed
dbLoader failures in resolvePermissionSets.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014y5kiH3aPLWtRRRGcVrXcT
… destructive-op gates
Correctness (record-level enforcement for the pre-mapped destructive ops):
- security-plugin middleware: extend the write-scope stash, the #1994
pre-image RLS check, and the ADR-0055 controlled-by-parent write gate to
cover transfer/restore/purge — the object-level RBAC bit was gated but the
row-level defenses only listed update/delete/insert, so a granted user
could have destroyed out-of-scope rows by id when the M2 ops ship. purge
maps onto the delete RLS class, transfer/restore onto update. New unit
test proves a not-owned purge is denied by the pre-image check.
Callsite coverage for the default flip:
- cli serve: carve-out now keys on (tierEnabled('auth') || hasAuthPlugin) —
a stack mounting AuthPlugin explicitly under a minimal tier no longer gets
an accidental fail-open override.
- plugin-dev: mirror the serve carve-out — an auth-less dev stack gets an
explicit requireAuth:false so the local data API isn't bricked.
- spec: declare ObjectStackDefinitionSchema.api so the documented opt-out
(and the enableProjectScoping/projectResolution/enforceProjectMembership
knobs serve.ts already reads) survives defineStack strict parsing instead
of being silently stripped. Verified: defineStack now returns the api key.
- rest-api-plugin: warn only on an explicit opt-out; add a misplaced-key
guard for a flat api.requireAuth (silently ignored under the deny default).
- fix two in-tree tests that passed requireAuth at the wrong nesting.
Cleanup:
- derive the modifyAllRecords write-bypass key set from OPERATION_TO_PERMISSION
+ DESTRUCTIVE_OPERATIONS (module-level Set, no per-call array) so a future
destructive op is covered automatically.
Docs / provenance:
- regenerate the auto-gen references (requireAuth + allow*{Transfer,Restore,
Purge} describe text); CHANGELOG entry for the breaking flip; changeset
migration note updated to the defineStack-level api opt-out + scope note.
Pre-existing anonymous-posture gap on /meta, dispatcher /graphql, and raw
hono /data (surfaces that never call enforceAuth) filed as #2567 — out of
scope for this flip.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014y5kiH3aPLWtRRRGcVrXcT
@os-zhuang
os-zhuang marked this pull request as ready for review July 4, 2026 08:18
@os-zhuang
os-zhuang merged commit 7c09621 into mainJul 4, 2026
17 checks passed
@os-zhuang
os-zhuang deleted the claude/production-launch-priorities-ps8hk3 branch July 4, 2026 08:25
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/lteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@os-zhuang@claude