Skip to content

feat(lint): warn when a master-detail child has no object-level CRUD grant (#2700) - #2721

Merged
os-zhuang merged 1 commit into
mainfrom
feat/lint-master-detail-ungranted-crud
Jul 10, 2026
Merged

feat(lint): warn when a master-detail child has no object-level CRUD grant (#2700)#2721
os-zhuang merged 1 commit into
mainfrom
feat/lint-master-detail-ungranted-crud

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Closes#2700.

Problem

A downstream app (os-tianshun-mtc) hit a silent DX trap: a master-detail child object was missing object-level CRUD in its permission sets, so role-bound non-admin users couldn't read/write the subtable — surfacing only as "can't fill in / can't submit the subtable" with no clear error. (downstream: steedos-labs/os-tianshun-mtc#43)

The root cause is two independent permission gates:

  1. Gate ① object-level CRUD (checkObjectPermission) — a permission set that doesn't list the object returns falsePermissionDeniedError (403).
  2. Gate ② record-level RLS / controlled_by_parent — decides which records, only after gate ① passes.

ADR-0055's controlled_by_parent lives entirely in gate ② (read injects masterFK IN (accessible masters), write requires master edit) and never derives gate ①. So "child access derives from the master" is a record-level guarantee only; object-level CRUD must still be granted per-object. Missing it, the cbp derivation never even runs — gate ① blocks first. This is deliberate secure-by-default (Salesforce parity); CRUD auto-inheritance is intentionally not adopted.

This "likely misconfiguration" is statically detectable but was flagged nowhere at compile time.

Change

New rule security-master-detail-ungranted (advisory warning, does not gate the build) in the ADR-0090 D7 security-posture linter (validateSecurityPosture).

Why the D7 linter, not data-model-rules.ts (the issue's first suggestion)

The rule cross-references objects × permissions. lintDataModel(objects) only receives objects (and feeds the metadata-eval scorer), whereas validateSecurityPosture(stack) already receives the full stack and is wired into both os lint and os compile — where only error gates, so a warning prints without blocking. This is the issue's stated alternative.

Verification

  • @objectstack/lint: 173 passed (13 new cases: incident shape, all 6 grant bits, '*' wildcard, array field form, system exemption, zero-permission-sets, path/message assertions).
  • @objectstack/cli (direct consumer): 334 passed — no snapshot/count drift.
  • tsc --noEmit: clean.
  • Ran against the built dist with an incident-shaped stack → 1 warning; granting the child → silent.

🤖 Generated with Claude Code

@vercel

vercelBot commented Jul 10, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
specReadyReadyPreview, CommentJul 10, 2026 3:40am

Request Review

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

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/lint.

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

  • content/docs/permissions/authorization.mdx(via @objectstack/lint)

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.

…grant (#2700)
New ADR-0090 D7 security-posture rule `security-master-detail-ungranted`
(advisory warning; does not gate the build). A master-detail detail object
derives its RECORD-level access from the master (ADR-0055 controlled_by_parent,
gate 2), but object-level CRUD is a SEPARATE gate 1 (checkObjectPermission)
that is never derived — a permission set that grants the parent but forgets the
child denies role-bound non-admin users a 403 before the parent-derived access
is ever consulted, the silent "can't submit the subtable" trap (framework#2700,
downstream os-tianshun-mtc#43).
Flags a non-system detail (has a master_detail field) that NO authored
permission set grants (explicit entry or '*' wildcard). Stays silent when the
package authors no permission sets, when a package-declared '*' wildcard grant
covers every object, or for sys_*/isSystem objects — keeping false positives
near zero. The per-set gap and CRUD auto-inheritance are deliberately out of
scope (secure-by-default, Salesforce parity).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@os-zhuang
os-zhuangforce-pushed the feat/lint-master-detail-ungranted-crud branch from 7a18d14 to feba25eCompareJuly 10, 2026 03:31
@os-zhuang
os-zhuang merged commit 2f3581f into mainJul 10, 2026
16 checks passed
@os-zhuang
os-zhuang deleted the feat/lint-master-detail-ungranted-crud branch July 10, 2026 03:43
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[平台/DX] 编译期 lint:master-detail 子对象在所有权限集中都无对象级 CRUD 授权时告警

1 participant

@os-zhuang