feat(lint): warn when a master-detail child has no object-level CRUD grant (#2700)#2721
Merged
Merged
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 1 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
…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
force-pushed
the
feat/lint-master-detail-ungranted-crud
branch
from
July 10, 2026 03:31
7a18d14 to
feba25e
Compare
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.
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:
checkObjectPermission) — a permission set that doesn't list the object returnsfalse→PermissionDeniedError(403).controlled_by_parent— decides which records, only after gate ① passes.ADR-0055's
controlled_by_parentlives entirely in gate ② (read injectsmasterFK 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(advisorywarning, does not gate the build) in the ADR-0090 D7 security-posture linter (validateSecurityPosture).master_detailfield) that no authored permission set grants object-level CRUD (explicit entry or'*'wildcard).'*'wildcard grant covers every object; or the object issys_*/isSystem. Keeps the false-positive rate near zero.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), whereasvalidateSecurityPosture(stack)already receives the full stack and is wired into bothos lintandos compile— where onlyerrorgates, so awarningprints 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.🤖 Generated with Claude Code