Repository navigation
GRANT … WHERE keeps a double-quoted name verbatim for most rule shapes — XPath string literal, the access rule silently matches nothing (check / exec / mx check all pass) #1243
Description
- mxcli version: v0.24.0 (2026-09-24T05:44:35Z)
- Mendix version: 11.14.0 (MPR v2)
- OS: Windows Server 2025
- Tracker searches: "grant xpath quote", "quoted identifier xpath", "access rule xpath string literal" → no hits. Related: Quoted attribute segment in nav expressions (
$Var/"Attr") parses but is invalid Mendix syntax #827 (quoted attribute in nav expressions, closed), Feature request: preserve or emit line breaks in XPath constraints #979 (canonical multi-line XPath, closed).
Summary
A double-quoted attribute name inside GRANT … WHERE '<xpath>' is stored verbatim for most rule shapes. In Mendix XPath "Status" is a string literal, so "Status" = 'Accepted' compares two strings, is always false, and the access rule silently grants no rows. For one rule shape mxcli rewrites the XPath (multi-line canonical form) and the quotes disappear, so the same habit is sometimes repaired and sometimes not.
mxcli check --references, mxcli lint, mx check and mx check -w all pass: the XPath is valid, just always false. In our app it was found only by a per-user runtime test (executives saw 0 rows of an entity their rule should have granted).
This matters because the generated CLAUDE.md / AGENTS.md says "Quote every identifier in MDL … Quotes are stripped, so it is always safe", so an agent reflexively quotes names inside the XPath string too (same trigger as #827). The same quoted name is stripped in a microflow retrieve … where.
Minimal repro (blank 11.14 project)
CREATE MODULE Rp;
CREATE OR MODIFY ENUMERATION Rp.St (Draft 'Draft', Accepted 'Accepted');
CREATE OR MODIFY PERSISTENT ENTITY Rp.Parent ("Name": String(50));
CREATE OR MODIFY PERSISTENT ENTITY Rp.Item ("Status": Enumeration(Rp.St), Kind: String(20));
CREATE OR MODIFY ASSOCIATION Rp.Item_Parent FROM Rp.Item TO Rp.Parent TYPE Reference;
CREATE OR MODIFY MODULE ROLE Rp.R1;
CREATE OR MODIFY MODULE ROLE Rp.R2;
CREATE OR MODIFY MODULE ROLE Rp.R3;
CREATE OR MODIFY MODULE ROLE Rp.R4;
CREATE OR MODIFY MODULE ROLE Rp.R5;
GRANT Rp.R1 ON Rp.Item (READ *) WHERE '[Status = ''Accepted'']';
GRANT Rp.R2 ON Rp.Item (READ *) WHERE '["Status" = ''Accepted'']';
GRANT Rp.R3 ON Rp.Item (READ *) WHERE '[Kind = ''a'' and "Status" = ''Accepted'']';
GRANT Rp.R4 ON Rp.Item (READ *) WHERE '["Status" = ''Accepted'' and Rp.Item_Parent/Rp.Parent["Name" = ''x'']]';
GRANT Rp.R5 ON Rp.Item (READ *) WHERE '[Kind = ''a'' and "Status" = ''Accepted'' and Rp.Item_Parent/Rp.Parent["Name" = ''x'']]';mxcli check g49.mdl -p Repro.mpr --references → Check passed!; mxcli exec → OK.
Actual (show access on entity Rp.Item, confirmed in the stored XPathConstraint)
| Rule | Written | Stored | Effect |
|---|---|---|---|
| R1 | [Status = 'Accepted'] |
same | correct |
| R2 | ["Status" = 'Accepted'] |
verbatim, quotes kept | never matches |
| R3 | [Kind = 'a' and "Status" = 'Accepted'] |
verbatim | never matches |
| R4 | ["Status" = 'Accepted' and Rp.Item_Parent/Rp.Parent["Name" = 'x']] |
verbatim | never matches |
| R5 | [Kind = 'a' and "Status" = 'Accepted' and Rp.Item_Parent/Rp.Parent["Name" = 'x']] |
rewritten: [Kind = 'a' and Status = 'Accepted' and Rp.Item_Parent/Rp.Parent[Name = 'x']] |
correct (by accident) |
mx check Repro.mpr → The app contains: 0 errors.; mxcli lint → only SEC001 for Rp.Parent.
For contrast, in a microflow the quotes are stripped:
CREATE OR MODIFY MICROFLOW Rp.MF_Q () RETURNS Integer
BEGIN
retrieve $L from Rp.Item where "Status" = 'Accepted';
$N = count($L);
return $N;
END;→ describe microflow Rp.MF_Q shows where Status = Rp.St.Accepted;
The bracket form that the skills describe as quote-safe for XPath (where [ … ]) is not accepted by GRANT:
GRANT Rp.R6 ON Rp.Item (READ *) WHERE ["Status" = 'Accepted']; → mismatched input '[' expecting STRING_LITERAL.
Expected
One consistent behaviour for GRANT … WHERE, in order of preference:
- strip identifier quotes in access-rule XPath as
retrieve … wherealready does (and as R5 happens to get); or - reject / warn in
check: "double-quoted name in XPath is a string literal — the rule never matches".
Suggestions
- Apply the same identifier-quote normalisation to every XPath string property (access rules, view-entity rules, page data-source XPath strings), not only when the multi-line formatter (Feature request: preserve or emit line breaks in XPath constraints #979) rewrites the constraint.
- Add a lint rule for a comparison whose left side is a string literal in any stored XPath constraint (it also catches rules made in Studio Pro).
- Qualify the "quotes are stripped, so it is always safe" line in the generated
CLAUDE.md/AGENTS.md: not inside XPath strings.
Workaround we use
Bare names in every XPath string; a text scan of MDL scripts before exec, and a post-exec scan of every stored XPathConstraint for "name" = / != / < / >; an as-user test for each access rule.
Session log (isolated HOME, this repro only)
{"time":"2026-09-30T21:49:10.3327987+03:00","level":"INFO","msg":"session_start","version":"v0.24.0","go":"go1.26.6","os":"windows","arch":"amd64","mode":"mxcli check","args":["C:\\Tools\\mxcli.exe","check","D:/Mohamed/Mendix/_mxrepro/scripts/g49.mdl","-p","Repro.mpr","--references"],"pid":49832,"parent_pid":""}
{"time":"2026-09-30T21:49:12.1945076+03:00","level":"INFO","msg":"connect","mpr_path":"Repro.mpr","mendix_version":"11.14.0","mpr_format":2}
{"time":"2026-09-30T21:49:12.1945076+03:00","level":"INFO","msg":"execute","stmt_type":"ConnectStmt","stmt_summary":"connect local 'Repro.mpr'","duration_ms":1}
{"time":"2026-09-30T21:49:12.7124484+03:00","level":"INFO","msg":"session_end","commands_executed":1,"errors_count":0,"duration_s":2,"pid":49832}
{"time":"2026-09-30T21:49:13.2549944+03:00","level":"INFO","msg":"session_start","version":"v0.24.0","go":"go1.26.6","os":"windows","arch":"amd64","mode":"mxcli exec","args":["C:\\Tools\\mxcli.exe","exec","D:/Mohamed/Mendix/_mxrepro/scripts/g49.mdl","-p","Repro.mpr"],"pid":48000,"parent_pid":""}
{"time":"2026-09-30T21:49:13.2892795+03:00","level":"INFO","msg":"connect","mpr_path":"Repro.mpr","mendix_version":"11.14.0","mpr_format":2}
{"time":"2026-09-30T21:49:13.2892795+03:00","level":"INFO","msg":"execute","stmt_type":"ConnectStmt","stmt_summary":"connect local 'Repro.mpr'","duration_ms":1}
{"time":"2026-09-30T21:49:13.7964319+03:00","level":"INFO","msg":"execute","stmt_type":"CreateModuleStmt","stmt_summary":"create module Rp","duration_ms":120}
{"time":"2026-09-30T21:49:13.9801527+03:00","level":"INFO","msg":"execute","stmt_type":"CreateEnumerationStmt","stmt_summary":"create enumeration Rp.St","duration_ms":183}
{"time":"2026-09-30T21:49:14.1423535+03:00","level":"INFO","msg":"execute","stmt_type":"CreateEntityStmt","stmt_summary":"create entity Rp.Parent","duration_ms":162}
{"time":"2026-09-30T21:49:14.3868236+03:00","level":"INFO","msg":"execute","stmt_type":"CreateEntityStmt","stmt_summary":"create entity Rp.Item","duration_ms":244}
{"time":"2026-09-30T21:49:14.6340449+03:00","level":"INFO","msg":"execute","stmt_type":"CreateAssociationStmt","stmt_summary":"create association Rp.Item_Parent","duration_ms":246}
{"time":"2026-09-30T21:49:14.6678397+03:00","level":"INFO","msg":"execute","stmt_type":"CreateModuleRoleStmt","stmt_summary":"create module role Rp.R1","duration_ms":33}
{"time":"2026-09-30T21:49:14.7773523+03:00","level":"INFO","msg":"execute","stmt_type":"CreateModuleRoleStmt","stmt_summary":"create module role Rp.R2","duration_ms":109}
{"time":"2026-09-30T21:49:14.8816998+03:00","level":"INFO","msg":"execute","stmt_type":"CreateModuleRoleStmt","stmt_summary":"create module role Rp.R3","duration_ms":104}
{"time":"2026-09-30T21:49:14.9892728+03:00","level":"INFO","msg":"execute","stmt_type":"CreateModuleRoleStmt","stmt_summary":"create module role Rp.R4","duration_ms":107}
{"time":"2026-09-30T21:49:15.0983088+03:00","level":"INFO","msg":"execute","stmt_type":"CreateModuleRoleStmt","stmt_summary":"create module role Rp.R5","duration_ms":109}
{"time":"2026-09-30T21:49:15.3957126+03:00","level":"INFO","msg":"execute","stmt_type":"GrantEntityAccessStmt","stmt_summary":"grant on entity Rp.Item","duration_ms":297}
{"time":"2026-09-30T21:49:15.5135647+03:00","level":"INFO","msg":"execute","stmt_type":"GrantEntityAccessStmt","stmt_summary":"grant on entity Rp.Item","duration_ms":117}
{"time":"2026-09-30T21:49:15.6305585+03:00","level":"INFO","msg":"execute","stmt_type":"GrantEntityAccessStmt","stmt_summary":"grant on entity Rp.Item","duration_ms":116}
{"time":"2026-09-30T21:49:15.7519818+03:00","level":"INFO","msg":"execute","stmt_type":"GrantEntityAccessStmt","stmt_summary":"grant on entity Rp.Item","duration_ms":121}
{"time":"2026-09-30T21:49:15.889849+03:00","level":"INFO","msg":"execute","stmt_type":"GrantEntityAccessStmt","stmt_summary":"grant on entity Rp.Item","duration_ms":137}
{"time":"2026-09-30T21:49:15.8959679+03:00","level":"INFO","msg":"session_end","commands_executed":16,"errors_count":0,"duration_s":2,"pid":48000}
{"time":"2026-09-30T21:49:16.4405609+03:00","level":"INFO","msg":"session_start","version":"v0.24.0","go":"go1.26.6","os":"windows","arch":"amd64","mode":"batch","args":["C:\\Tools\\mxcli.exe","-p","Repro.mpr","-c","show access on entity Rp.Item"],"pid":40480,"parent_pid":""}
{"time":"2026-09-30T21:49:16.4935548+03:00","level":"INFO","msg":"connect","mpr_path":"Repro.mpr","mendix_version":"11.14.0","mpr_format":2}
{"time":"2026-09-30T21:49:16.4935548+03:00","level":"INFO","msg":"execute","stmt_type":"ConnectStmt","stmt_summary":"connect local 'Repro.mpr'","duration_ms":2}
{"time":"2026-09-30T21:49:16.5733963+03:00","level":"INFO","msg":"execute","stmt_type":"ShowStmt","stmt_summary":"show ACCESS ON ENTITY Rp.Item","duration_ms":79}
{"time":"2026-09-30T21:49:16.5737403+03:00","level":"INFO","msg":"session_end","commands_executed":2,"errors_count":0,"duration_s":0,"pid":40480}
{"time":"2026-09-30T21:49:26.6633269+03:00","level":"INFO","msg":"session_start","version":"v0.24.0","go":"go1.26.6","os":"windows","arch":"amd64","mode":"mxcli lint","args":["C:\\Tools\\mxcli.exe","lint","-p","Repro.mpr"],"pid":13824,"parent_pid":""}
{"time":"2026-09-30T21:49:26.7004913+03:00","level":"INFO","msg":"connect","mpr_path":"Repro.mpr","mendix_version":"11.14.0","mpr_format":2}
{"time":"2026-09-30T21:49:26.7004913+03:00","level":"INFO","msg":"execute","stmt_type":"ConnectStmt","stmt_summary":"connect local 'Repro.mpr'","duration_ms":2}
{"time":"2026-09-30T21:49:28.748318+03:00","level":"INFO","msg":"execute","stmt_type":"RefreshCatalogStmt","stmt_summary":"refresh catalog","duration_ms":2046}
{"time":"2026-09-30T21:49:28.8200773+03:00","level":"INFO","msg":"session_end","commands_executed":2,"errors_count":0,"duration_s":2,"pid":13824}
{"time":"2026-09-30T21:49:29.3874928+03:00","level":"INFO","msg":"session_start","version":"v0.24.0","go":"go1.26.6","os":"windows","arch":"amd64","mode":"mxcli exec","args":["C:\\Tools\\mxcli.exe","exec","D:/Mohamed/Mendix/_mxrepro/scripts/g49_retrieve.mdl","-p","Repro.mpr"],"pid":10584,"parent_pid":""}
{"time":"2026-09-30T21:49:29.4243662+03:00","level":"INFO","msg":"connect","mpr_path":"Repro.mpr","mendix_version":"11.14.0","mpr_format":2}
{"time":"2026-09-30T21:49:29.4243662+03:00","level":"INFO","msg":"execute","stmt_type":"ConnectStmt","stmt_summary":"connect local 'Repro.mpr'","duration_ms":2}
{"time":"2026-09-30T21:49:29.8757364+03:00","level":"INFO","msg":"execute","stmt_type":"CreateMicroflowStmt","stmt_summary":"create microflow Rp.MF_Q","duration_ms":38}
{"time":"2026-09-30T21:49:29.8784214+03:00","level":"INFO","msg":"session_end","commands_executed":2,"errors_count":0,"duration_s":0,"pid":10584}
{"time":"2026-09-30T21:49:30.4818638+03:00","level":"INFO","msg":"session_start","version":"v0.24.0","go":"go1.26.6","os":"windows","arch":"amd64","mode":"batch","args":["C:\\Tools\\mxcli.exe","-p","Repro.mpr","-c","describe microflow Rp.MF_Q"],"pid":49288,"parent_pid":""}
{"time":"2026-09-30T21:49:30.5310811+03:00","level":"INFO","msg":"connect","mpr_path":"Repro.mpr","mendix_version":"11.14.0","mpr_format":2}
{"time":"2026-09-30T21:49:30.5310811+03:00","level":"INFO","msg":"execute","stmt_type":"ConnectStmt","stmt_summary":"connect local 'Repro.mpr'","duration_ms":2}
{"time":"2026-09-30T21:49:30.713686+03:00","level":"INFO","msg":"execute","stmt_type":"DescribeStmt","stmt_summary":"describe MICROFLOW Rp.MF_Q","duration_ms":182}
{"time":"2026-09-30T21:49:30.7142077+03:00","level":"INFO","msg":"session_end","commands_executed":2,"errors_count":0,"duration_s":0,"pid":49288}
{"time":"2026-09-30T21:49:31.2529343+03:00","level":"INFO","msg":"session_start","version":"v0.24.0","go":"go1.26.6","os":"windows","arch":"amd64","mode":"mxcli check","args":["C:\\Tools\\mxcli.exe","check","D:/Mohamed/Mendix/_mxrepro/scripts/g49_bracket.mdl","-p","Repro.mpr"],"pid":43128,"parent_pid":""}
Activity
fix(xpath): a double-quoted value in an XPath is refused; a double-qu…
ako commentedon Oct 2, 2026
Thanks for reporting this. It's fixed on
main(f6f3956, synced in #1259): a double-quoted name in grant … where / XPath is stripped, and a double-quoted value is refused naming the single-quoted form. The fix is ako#919, with a regression test. Closing; if you still see it with a build from today or later, please reopen with a reproduction.fix(visitor): strip quoted names in a multi-predicate retrieve constr…