feat(security): 权限集「两道门」分离 — 包门发布物化 + 管理门写门 (ADR-0086 P2)#2573
Merged
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
📓 Docs Drift CheckThis PR changes 3 package(s): 11 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
18 tasks
Splits who may change a permission set into two non-overlapping doors, enforced at the data layer instead of by convention. 块1 — package door (publish-time materialization): - metadata-protocol: generic publish-time materializer registry (registerPublishMaterializer); publishMetaItem projects a published body into its data-plane row and surfaces materializeApplied (best-effort, never thrown — same contract as seedApplied). publishPackageDrafts aggregates it across the batch so a refused set is surfaced, not swallowed. - promoteDraft now returns the draft's packageId so the materializer stamps the owning package. - plugin-security registers a `permission` materializer that upserts the published set into sys_permission_set with managed_by:'package' + package_id. The single-set upsert (upsertPackagePermissionSet) is shared with bootstrapDeclaredPermissions, so boot and publish apply identical own-row / foreign-package / env-authored rules. A publish that materializes nothing (no owning package / name owned elsewhere) reports success:false. 块2 — admin door (data-layer write gate): - The security middleware refuses any admin-door write to a sys_permission_set row with managed_by:'package', and refuses a payload (insert OR update, single object OR array) that forges managed_by:'package'. - Placed before the empty-principal fall-open and the CRUD check, so it is a real, unconditional boundary — it holds for a principal-less context and for a superuser with modifyAllRecords. A multi-row/filter write is denied only when a package-owned row actually falls within the write's own filter, so env-only bulk edits still succeed. System/boot writes carry isSystem and bypass the middleware, so the seeder and materializer are unaffected. Tested: unit (materialize semantics, gate incl. array-forge / update-to-forge / principal-less / precise-bulk) + a dogfood test booting the real showcase stack that publishes a package permission draft and drives the admin door against the seeded package row over REST. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014y5kiH3aPLWtRRRGcVrXcT
os-zhuang
force-pushed
the
claude/production-launch-priorities-ps8hk3
branch
from
July 4, 2026 12:09
8a106ad to
b625994
Compare
os-zhuang
marked this pull request as ready for review
July 4, 2026 12:38
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.
概述
落地 ADR-0086 P2「两道门分离」 的 framework 侧,把"谁能改一个权限集"拆成两扇互不重叠、且在数据层强制的门(不再靠约定):
permission元数据草稿 → 发布时物化进sys_permission_set。块1 — 包门:发布时物化(路线①,复用既有元数据流水线)
metadata-protocol/protocol.tsregisterPublishMaterializer(type, fn);publishMetaItem在 promote 后调用对应物化器,把结果作为materializeApplied挂在响应上(best-effort、绝不 throw——与seedApplied同契约)。publishPackageDrafts批量路径聚合materializeApplied,让批量发布里被拒的集不再被静默吞掉metadata-protocol/sys-metadata-repository.tspromoteDraft现在返回草稿的packageId,物化器据此盖上归属包plugin-securitypermission物化器:把已发布集 upsert 进sys_permission_set(managed_by:'package'+package_id)。单集 upsert(upsertPackagePermissionSet)与bootstrapDeclaredPermissions共用,所以开机播种与运行时发布走同一套 own-row / 外包拒绝 / env 自建永不覆盖 规则。物化落空(无归属包 / 名字被别处占用)时报success:falsepermission类型本就supportsOverlay/allowRuntimeCreate/allowOrgOverride/supportsVersioning全开,REST/meta/*?mode=draft+/meta/*/publish已就绪——几乎零框架新增,只加了"发布→物化"这座桥。块2 — 管理门:数据层写门(硬拒绝)
managed_by:'package'的sys_permission_set行的写(update/delete/transfer/restore/purge),并拒绝任何伪造managed_by:'package'的负载(insert 或 update、单对象或数组)。modifyAllRecords的超级用户都成立。isSystem,在中间件最前端短路——播种器与物化器自身的写不会被自己的门挡住。验证
plugin-security173 passed、metadata-protocol8 passed、objectqlpublish-package-drafts 10 passed。managed_by:'package'+package_id的行;再经 REST 驱动管理门——改包行 403、改 env 行 200、insert 伪造 provenance 403。4 passed。自审
已用多角度自审(逐行 + removed-behavior + cross-file),发现并修复:数组 insert 绕过伪造门、update 改badge 伪造、无归属包误报 success、批量发布丢弃物化结果、写门在 fall-open 之后不够无条件、批量写门过度封锁——均已修并补测。
关联
package_id/managed_by/bootstrapDeclaredPermissions;本 PR 为 P2)@objectstack/metadata-protocolminor、@objectstack/plugin-securityminor🤖 Generated with Claude Code
https://claude.ai/code/session_014y5kiH3aPLWtRRRGcVrXcT
Generated by Claude Code