Repository navigation
Description
Part of objectstack-ai/hotcrm#703
Found during HotCRM's 17.0 GA acceptance sweep on @objectstack/* 17.0.0-rc.2, 5-profile permission matrix phase (188 probes, each user with their own Bearer token).
Symptom
The platform's member_default permission set carries object_permissions["*"] = {allowCreate:true, allowRead:true, allowEdit:true, allowDelete:false} and is union-merged (most-permissive) into every org member. An app's explicit-allow object gate is therefore erased on the create, read and edit axes. Only delete stays profile-driven.
security/explain states it outright — for a profile carrying an explicit all-false deny on crm_opportunity:
> create on 'crm_opportunity' is granted by [member_default]
Measured (HotCRM, 17 objects, 5 profiles)
- 21 create-denial probes, ALL returned 201 — every profile creates on every object once validation passes (incl. objects the profile explicitly denies).
member_defaultalso grants own-row EDIT on objects the profile forbids editing (service_agent edited its owncrm_account, profile declares no edit anywhere).- On public_read objects the wildcard yields 200 with ALL rows for non-holders — real unauthorized reads, not the documented "200 with 0 rows" empty-set pattern.
Reproduction
# as a user whose profile carries an explicit all-false deny on crm_opportunity
POST /api/v1/data/crm_opportunity {"name":"X","stage":"prospecting","amount":1,"close_date":"2026-09-01", ...}
→ 201
GET /api/v1/security/explain?object=crm_opportunity&operation=create
→ "... granted by [member_default]"Why this matters
The app declares explicit-allow; the platform enforces default-allow-except-delete. App-side authorization test suites validate declarations, so CI is green while the runtime posture is default-open — declared ≠ enforced in the security layer itself. If the wildcard is intentional platform policy, apps need a supported opt-out: explicit deny entries do not win against the union today.
Prior related issues (checked, none open on this defect)
- Builtin member_default carries anchor-forbidden bits — every boot logs 'refusing to bind fallback set to everyone' (platform baseline violates its own D5 tier) #2753 (closed) — member_default anchor-forbidden bits at boot
- 澄清:viewAllRecords / modifyAllRecords 在 public_read_write 下失效,是否为开源版有意设计? #3005 (closed) — clarification of viewAll/modifyAll under
public_read_write; this report is aboutmember_default's wildcard under normal OWDs - [security][P0] Broken access control — any authenticated member can read AND modify other users' records #1985 (closed) — the 16.x-era broad-access defect
Full probe log: HotCRM acceptance run w4/results.jsonl (M1/M9). Sibling findings from the same sweep: objectstack-ai/hotcrm#705 (write gate), objectstack-ai/hotcrm#704 (parent-derived reads, tracked here as #5386).
Blocked-by: #6428
Activity
hotlong commentedon Aug 7, 2026
Triage (maintainer-directed batch, 2026-08-07): routed
domain:identity—member_default's*wildcard C/R/E grant is defined in plugin-security's default permission sets, and the erasing union-merge lives in its PermissionEvaluator.bug+target:v17retained. Anchored by code read; triage seat may veto.Generated by Claude Code
baozhoutao commentedon Aug 7, 2026
转决策箱(identity 车道 PM,
session_01JwwiU9bjhwy2SWj13ho8uv,第 14 轮;加needs-user-decision,建议与 #5492 同席裁决 —— 两单语义耦合,#5492 comment 5217346436 有连带说明)。具体问题:
member_default的*通配符 C/R/E 授权 union 进每个 org member,app 显式 allow 门在三轴上被抹掉(21 条 create 拒绝探针全部 201;显式全 false 的 profile 照样建单);且今天显式 deny 不胜 union,app 无受支持的退路。可选方案(三族,均越出 PM 授权面 —— 改的都是每个部署的默认安全姿态或权限求值公开语义):
modifyAllRecordsnorsys_record_share.access_level— both declared write-widening mechanisms are inert #5492 的 modifyAllRecords 同一 union 通道(dev 实测确认耦合);三轴粗评:实际业务(HotCRM 188 探针,5 profile 全部被抹平 —— 真实拉动,非投机);长期(declared ≠ enforced 落在安全层自身,与 ADR-0049 直接冲突;#2753 先例说明 member_default 的边界曾被专门收束过);防 AI 错误(A/B 都让「显式声明生效」结构性成立;C 最弱)。倾向 A 或 B,但两者的迁移代价与 #5492 的裁决方向强相关,故不单独预裁,提请同席定夺。
Generated by Claude Code
os-project-manager commentedon Aug 7, 2026
Maintainer ruling — 2026-08-07. The maintainer reviewed the PM's three-axis analysis of the decision inbox and accepted the recommendations (「你帮我综合评估,我接受你的建议」). Recorded by the PM session;
needs-user-decisioncomes off with this comment.Decision: remove the
*wildcard object grant frommember_defaulton all three live axes (create / read / edit; delete is already false). The platform baseline narrows to explicit-allow: object access comes from OWDs plus profile / permission-set declarations only. Deny-precedence merge semantics were considered and rejected — changing union-merge globally is a larger contract change than deleting the one grant that poisons it.Rationale (three axes): measured erasure of app-declared explicit-allow (21/21 create-denial probes returned 201; real unauthorized reads on
public_read); declared ≠ enforced inside the security layer itself; a hidden most-permissive union is unlearnable for AI-authored app metadata — CI stays green while the runtime posture is default-open.Constraints for the implementing lane:
modifyAllRecordsnorsys_record_share.access_level— both declared write-widening mechanisms are inert #5492 — once the wildcard is gone, cross-owner writes depend entirely on the declared wideners Row-level write gate consults neithermodifyAllRecordsnorsys_record_share.access_level— both declared write-widening mechanisms are inert #5492 turns on. Landing either card alone strands managers or keeps the hole.Generated by Claude Code
os-zhuang commentedon Aug 8, 2026
Claim: PM loop round 1 (identity seat) — paired dispatch, this card + #5492, one dev, one PR (maintainer ruling 2026-08-07 16:59Z on both cards: same batch — removing the wildcard without enabling the declared wideners strands every manager workflow; enabling wideners without removing the wildcard keeps the hole)
Session:
session_01BM1tNf5U3nEbHKR4fo5qVQBranch:
claude/issue-5492-write-gate-pair(shared with #5492; PR will carryFixes #5492+Fixes #5491)Worktree:
objectstack-issue-5492Domain:
domain:identityFile surface: identical to the #5492 claim —
packages/plugins/plugin-security/src/(security-plugin.ts, objects/default-permission-sets.ts, tests) +.changeset/(with migration narrative per the ruling).Serial constraints cleared: see the #5492 claim comment (posted in the same stroke); blocker #6428 → PR #6564 MERGED, verified.
Container class: M —
mode:subagent(shared container).Generated by Claude Code
test(dogfood): migrate fixtures off the removed member_default wildca…
fix(plugin-security): enforce both declared write-wideners; baseline …
docs(changeset): declare the ADR-0087 disposition for the #5491 break…
os-zhuang commentedon Aug 8, 2026
ACCEPT — PR #6684 (paired with #5492; identity seat, round 1 review). Full review trail on #5492 (comment posted in the same stroke); the #5491 half, verified specifically:
'*'grant is removed frommember_default(all three live axes; delete was already profile-driven) — the baseline narrows to explicit-allow per the ruling; deny-precedence was NOT implemented (ruling rejected it; sets stay additive per ADR-0090).sys_user_preferenceself-service converted implicit→explicit entry (effective member access byte-identical); identity-table reads kept, writes still denied;owner_only_*RLS policies and anchor-safety untouched; admin sets keep their deliberate wildcards.member-default-explicit-allow.test.tspins the new posture on the real merge path, including the exact "granted by [member_default]" union this issue measured — an explicit all-false profile now stays denied after the merge.security/explainas the pre-upgrade audit tool — per the ruling's constraint.EXPECTED_MATRIXcell is unchanged; dogfood boots the showcase's own default profile the way the CLI does; 安全:静态字段 readonly: true 仅 UI 层生效、服务端不强制 → 审批/状态/金额字段可被直接 PATCH 绕过写入(FLS 缺口) #3003's non-admin property preserved with named grants, not a wildcard).Generated by Claude Code
fix(security): grant members owner-scoped read on the personal inbox (o…
fix(security): drop the '*' export grant from the shipped admin permi…
docs(plugin-security): correct the managed-write-denies docblock on m…
docs(plugin-security): correct the managed-write-denies docblock on m…