Skip to content

member_default's * wildcard object grant (C/R/E) union-merges into every org member — app-side explicit-allow object gates are erased on three axes #5491

Closed

Description

@yinlianghui

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_default also grants own-row EDIT on objects the profile forbids editing (service_agent edited its own crm_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)

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

  1. added
    bugSomething isn't working
    pm:queueReady for the PM dispatch loop
    on Aug 5, 2026
  2. added
    target:v17Release blocker for v17 — on the board until fixed, dropped as no longer valid, or accepted for GA
    on Aug 6, 2026
  3. hotlong commented on Aug 7, 2026

    @hotlong
    Contributor

    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:v17 retained. Anchored by code read; triage seat may veto.


    Generated by Claude Code

  4. added
    needs-user-decisionBlocked on a maintainer decision — do not dispatch; decision cards only, never on a PR
    and removed
    pm:queueReady for the PM dispatch loop
    on Aug 7, 2026
  5. baozhoutao commented on Aug 7, 2026

    @baozhoutao
    Contributor

    转决策箱(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 授权面 —— 改的都是每个部署的默认安全姿态或权限求值公开语义):

    三轴粗评:实际业务(HotCRM 188 探针,5 profile 全部被抹平 —— 真实拉动,非投机);长期(declared ≠ enforced 落在安全层自身,与 ADR-0049 直接冲突;#2753 先例说明 member_default 的边界曾被专门收束过);防 AI 错误(A/B 都让「显式声明生效」结构性成立;C 最弱)。倾向 A 或 B,但两者的迁移代价与 #5492 的裁决方向强相关,故不单独预裁,提请同席定夺。


    Generated by Claude Code

  6. os-project-manager commented on Aug 7, 2026

    @os-project-manager
    Collaborator

    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-decision comes off with this comment.

    Decision: remove the * wildcard object grant from member_default on 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:


    Generated by Claude Code

  7. added
    pm:queueReady for the PM dispatch loop
    and removed
    needs-user-decisionBlocked on a maintainer decision — do not dispatch; decision cards only, never on a PR
    on Aug 7, 2026
  8. added
    pm:blockedBlocked by another issue/PR — body carries Blocked-by: #N
    on Aug 7, 2026
  9. self-assigned this
    on Aug 8, 2026
  10. os-zhuang commented on Aug 8, 2026

    @os-zhuang
    Contributor

    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_01BM1tNf5U3nEbHKR4fo5qVQ
    Branch: claude/issue-5492-write-gate-pair (shared with #5492; PR will carry Fixes #5492 + Fixes #5491)
    Worktree: objectstack-issue-5492
    Domain: domain:identity
    File 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

  11. os-zhuang commented on Aug 8, 2026

    @os-zhuang
    Contributor

    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:

    • The '*' grant is removed from member_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_preference self-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.
    • 25-case member-default-explicit-allow.test.ts pins 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.
    • Reverse verification: wildcard restored → exactly those 9 pins red, nothing else.
    • Migration narrative ships as a breaking changeset naming the intended behavior change, the two supported re-declaration paths, and security/explain as the pre-upgrade audit tool — per the ruling's constraint.
    • Fixture blast radius triaged case-by-case (matrix roles re-declare the removed grant byte-for-byte so every EXPECTED_MATRIX cell 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingpm:dispatchedDispatched to a dev agent by /pm-dispatchpm:queueReady for the PM dispatch looptarget:v17Release blocker for v17 — on the board until fixed, dropped as no longer valid, or accepted for GA

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions