Skip to content

fix: security writes stripped inherited members from access rules (#758, #765) - #81

Merged
ako merged 3 commits into
mainfrom
claude/generalization-chain-765-758
Aug 2, 2026
Merged

fix: security writes stripped inherited members from access rules (#758, #765)#81
ako merged 3 commits into
mainfrom
claude/generalization-chain-765-758

Conversation

@ako

@ako ako commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Fixes mendixlabs/mxcli#758 and the root cause in #765.

Problem

Mendix models inheritance across multiple tables: a child adds attributes to the parent's, and all of the parent's are members of the child. An access rule must therefore carry a MemberAccess entry for every member — own and inherited — or Mendix reports CE0066 "Entity access is out of date".

Both the GRANT builder and ReconcileMemberAccesses enumerated only entity.Attributes. Two consequences, and the second explains why the first couldn't be worked around:

  • GRANT naming an inherited member produced no entry at all, while reporting success.
  • Reconciliation runs immediately after every GRANT, and on any write touching the module. An inherited reference is qualified against the entity that declares it, so it never matched the child's own attribute list and was deleted as stale — removing, in the same command, what the grant had just written correctly.

That last point is the whole issue: it is why REVOKE + GRANT never repaired a damaged rule, and it is not visible from the grant code alone.

The damage was masked. mx check reports CE0066 and stops, hiding the CE2729 "No read access to attribute" cascade until Studio Pro's Update security is clicked — so CLI-only workflows shipped it undetected.

Two facts, established against mx check rather than inferred

1. An inherited reference is qualified against its declaring entity.

stored reference mx check
Sec758.Item.SharedField (child-qualified — what mxcli wrote) CE1613 "The selected attribute no longer exists"
Sec758.Base.SharedField (declaring entity) 0 errors, CE0066 cleared

This is the same rule the change-object writer needs (mendixlabs#451).

2. System.User is the exception. Entities specialising it are user entities whose platform members Mendix manages:

ancestor inherited entries present? result
Sec758.Base (same module) no CE0066
Sec758.Base yes 0 errors
System.FileDocument no CE0066
System.FileDocument all 6 0 errors
System.User no 0 errors
System.User 4 of 9, or all 9 CE0066

Verified on Mendix's own Administration.Account and on a fresh specialisation, with a no-op-rewrite control confirming that merely touching the module doesn't itself trigger CE0066.

Approach

  • EntityMembers (new, mdl/executor/entity_hierarchy.go) walks the generalization chain, qualifies each member against its declaring entity, handles child-shadows-ancestor, guards against cycles, and excludes System.User's platform members.
  • The GRANT builder uses it, and now rejects a named member that matched nothing instead of dropping it silently — the behaviour that made the bug unrepairable.
  • Reconciliation strips only a reference qualified to the entity itself. An ancestor may live in another module or in System, neither of which is loaded at that layer, so an inherited reference cannot be validated there at all — it is preserved rather than deleted. Applied to both engines (mdl/backend/modelsdk and sdk/mpr), which had separate copies of the same defect.

Verification

End-to-end on a real 11.12.2 project carrying all three specialisation shapes at once — same-module ancestor, System.FileDocument, and System.User:

mx check  ->  The app contains: 0 errors.
describe  ->  grant Sec758.Editor on Sec758.Item (read (OwnField, SharedField));

Stored refs are Sec758.Item.OwnField + Sec758.Base.SharedField; Attachment gets 6 System.FileDocument entries; Employee gets 0 System.User entries.

All three guards mutation-checked — removing the chain walk, the System.User exclusion, or the reconciler's ownership check each reproduces the reported symptom. Full ./... suite green.

Repro script: mdl-examples/bug-tests/758-inherited-member-access.mdl.

Scope

This fixes mendixlabs#758 and the security half of the mendixlabs#765 umbrella. mendixlabs#765 also covers mendixlabs#703 (import/export mapping skips inherited attributes) — untouched here — and mendixlabs#451, whose declaring-entity rule the microflow path already implements via resolveAttributeInEntityHierarchy. EntityMembers is deliberately ctx-based and reusable, so mendixlabs#703 can adopt it.

Generalisable lesson (recorded in the symptom table)

When a post-write reconcile pass validates against a narrower model than the writer used, it will quietly undo correct writes. Check what runs after a write before concluding the writer is at fault — the grant code here was fixed first and appeared to change nothing.


Generated by Claude Code

claude added 3 commits August 2, 2026 04:40
…ndixlabs#758, mendixlabs#765)

Mendix models inheritance across multiple tables: a child adds attributes to the
parent's, and all of the parent's are members of the child. An access rule must
therefore carry a MemberAccess entry for every member — own AND inherited — or
Mendix reports CE0066 "Entity access is out of date".

Both the GRANT builder and ReconcileMemberAccesses enumerated only
entity.Attributes. Two consequences, and the second explains why the first could
not be worked around:

  * GRANT naming an inherited member produced no entry at all, while reporting
    success.
  * Reconciliation runs immediately after every GRANT, and on any write touching
    the module. An inherited reference is qualified against the entity that
    DECLARES it, so it never matched the child's own attribute list and was
    deleted as stale — removing, in the same command, what the grant had just
    written. That is why REVOKE + GRANT never repaired a damaged rule.

The damage was masked: mx check reports CE0066 and stops, hiding the CE2729
"No read access to attribute" cascade until Studio Pro's Update security is
clicked, so CLI-only workflows shipped it undetected.

Two facts were established against mx check rather than inferred:

  1. An inherited member's reference must be qualified against its declaring
     entity. Sec758.Base.SharedField validates clean; the child-qualified
     Sec758.Item.SharedField is CE1613 "The selected attribute no longer exists".
     mxcli wrote the child form. This is the same rule the change-object writer
     needs (mendixlabs#451).
  2. System.User's members are the exception. Entities specialising it are user
     entities whose platform members Mendix manages: listing them turns a clean
     rule into CE0066 — confirmed on Mendix's own Administration.Account and on a
     fresh specialisation — while omitting System.FileDocument's six members is
     CE0066 until all are present.

Fixed:

  * EntityMembers walks the generalization chain, qualifying each member against
    its declaring entity and excluding System.User's platform members. The GRANT
    builder uses it, and now rejects a named member that matched nothing instead
    of dropping it in silence.
  * Reconciliation strips only a reference qualified to the entity itself. An
    ancestor may live in another module or in System, neither of which is loaded
    at that layer, so an inherited reference cannot be validated there — it is
    preserved rather than deleted. Applied to both engines.

Verified end-to-end on a real 11.12.2 project carrying all three specialisation
shapes at once — same-module ancestor, System.FileDocument, and System.User —
mx check reports 0 errors, and describe round-trips both members of the mixed
entity. All three guards mutation-checked.

Refs mendixlabs#758, mendixlabs#765
Nothing about entity inheritance appeared in any security doc, even though a
specialized entity's access rule must cover its inherited members and getting it
wrong is CE0066. Added to each surface the story touches:

- mxcli syntax security.entity-access — an "Inherited members" block plus
  examples for a same-module ancestor and System.FileDocument
- skills/mendix/manage-security.md — worked example, the None-rights detail, the
  new unknown-member error, and the System.User exception
- skills/mendix/generate-domain-model.md — a pointer from EXTENDS, where a reader
  meets inheritance first
- docs-site security/grant.md — the same as reference prose
- MDL_QUICK_REFERENCE.md — the grant-entity-access row

Covers what mendixlabs#758/mendixlabs#765 made work: inherited members are named exactly like the
entity's own, READ */WRITE * include them, unmatched names are an error rather
than a silent skip, and entities extending System.User must not grant their
inherited platform members.
@ako
ako merged commit b98197a into main Aug 2, 2026
3 of 5 checks passed
ako pushed a commit that referenced this pull request Aug 2, 2026
The doctype example granted read on SecTest.Customer (Notes), but the entity
only declares Name, Email and IsActive. Before mendixlabs#758 an unmatched member name was
dropped in silence, so the grant did nothing and the script still passed; with
that silence replaced by an error the example fails, and the integration tier
caught it.

The example is what is wrong: its own comment says "adding Notes access
preserves existing Name and Email", so it always meant to demonstrate an
additive grant on a third attribute. Declaring Notes makes it do that.

Fixes the build-and-test failure on main introduced by #81.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants