<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Johan Flikweert on Medium]]></title>
        <description><![CDATA[Stories by Johan Flikweert on Medium]]></description>
        <link>https://medium.com/@johan.flikweert?source=rss-a98e8e51ba32------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*B40dywgo0tdaFtuntDsvOQ.jpeg</url>
            <title>Stories by Johan Flikweert on Medium</title>
            <link>https://medium.com/@johan.flikweert?source=rss-a98e8e51ba32------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Fri, 22 May 2026 13:14:59 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@johan.flikweert/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Why you should disallow anonymous users in Mendix]]></title>
            <link>https://medium.com/@johan.flikweert/why-you-should-disallow-anonymous-users-in-mendix-45aebdd10615?source=rss-a98e8e51ba32------2</link>
            <guid isPermaLink="false">https://medium.com/p/45aebdd10615</guid>
            <category><![CDATA[authentication]]></category>
            <category><![CDATA[mendix]]></category>
            <category><![CDATA[sso]]></category>
            <dc:creator><![CDATA[Johan Flikweert]]></dc:creator>
            <pubDate>Wed, 25 Feb 2026 08:12:16 GMT</pubDate>
            <atom:updated>2026-03-05T09:19:05.439Z</atom:updated>
            <content:encoded><![CDATA[<p>Almost all Mendix applications are designed for a small group of users, which first require to log in to use it. When security is enabled, the default behaviour of Mendix is to redirect users from the landing page to login.html, where basic login functionality is provided. So far, so good, until developers implement some form of Single Sign On, Forgot Password or a nicely styled login page and enable the Anonymous Users functionality. Unintentionally, an important layer of defence has been torn down: probably all 5 billion internet users are now enabled to initiate a session in the application. Security-wise, this is a big trade-off for whatever the developer wanted to achieve.</p><p>Can’t Mendix applications with anonymous users be safe? Of course they can, but the attack surface is much bigger, since for every microflow, entity and page you have to determine what access is granted to the Anonymous User. This is acknowledged by Mendix:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/940/1*bmH7G4zOuw5ph-u_2beG6w.png" /></figure><p>Sure, if you need a web portal application, you do require Anonymous Users to use your application, but most applications simply don’t. Why grant the whole world a Mendix session in your application before any authentication took place?</p><p>When configuring entity access, we know that granting access is as easy as ticking a checkbox. Experience shows that access to data is granted too easily for Anonymous Users in the same way. When this concerns sensitive data and gets deployed, you could have a serious data breach, exploitable for everybody. For sure, in the development process you always need to review changes, but it can slip through. Strong security always relies on multiple layers of defence to lower the risk if one layer is broken or breached.</p><p>Before explaining how the login process works and how one should implement SSO without anonymous users, let me first illustrate the layers of security I want to highlight.</p><p>In my illustration, I show three important layers of security I want every Mendix developer to consider.</p><ul><li>The first layer is using a private network: if your Mendix application is only accessible from your internal network, this gives a huge security benefit, as this narrows the attack surface drastically. This requires having an internal infrastructure, including the capability to maintain this properly, which not every company has or is willing to have.</li><li>The second layer is about allowing Anonymous Users, a setting which is turned off by default, and often turned on for simplicity reasons. Hopefully you know by the end of this blog post why you should always try to keep it turned off.</li><li>The third layer is having SSO implemented together with a module that blocks Mendix logins. Having SSO implemented is great, having MFA configured in your Identity Provider is even better, and having the local Mendix Login disabled is the best, as the (weak) passwords of local users are out of the equation.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Nj0pYd7WtTcvIg9LpID5IA.png" /></figure><p>As the second layer of security is actionable for most developers, I want to focus on how we can avoid using Anonymous Users without detracting the user experience of the login process. Let’s deep dive into the login process!</p><p><strong>How the login process works</strong></p><p>Unfortunately, the Mendix documentation is somewhat lacking on the inner workings: the login process is not clearly written out, while you do want to make some modifications when implementing some form of SSO. On the OIDC documentation page there are no instructions at all how to configure this without Anonymous Users. In the SAML and Mendix SSO documentation there are some hints and options described, but it’s missing clear instructions. That doesn’t really help for us developers to build secure apps.</p><p>So how does the login process work exactly? First, the browser retrieves all static files like <em>index.html, mxui.js, theme.compiled.css</em>, then it starts the Mendix Client Javascript, which tries to retrieve the session data. If Anonymous Users are enabled, this always succeeds and creates a new user and new session if none exists. If Anonymous Users are not enabled, this fails and a 401 Unauthorized is returned. This “error” is caught by the Mendix Client and redirects the browser to the login page. As you can see in the GIF below: first the static files are load, then the Network shows a 401 for the /xas/ endpoint, which triggers navigation to <em>/login.html</em>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*4OTROy1pw4M-ABTY5pPwlA.gif" /></figure><p>Now the magic redirect to <em>/login.html</em> makes you wonder where that knowledge is coming from and more importantly: how we can manipulate that.</p><p>In the<em> index.html</em> file, a small inline code block of JavaScript is written to set the ‘originURI’ cookie. This code is run once on load of the landing page and is used when authorization fails. By default Mendix sets this to <em>/login.html</em>, and this is the configuration we can adjust to our needs:</p><ul><li>If you just want to use Mendix login functionality without any Single Sign On functionality, I recommend to use the default <em>login.html</em> page. You can personalize this using HTML and (S)CSS in the theme/web folder.</li><li>If you do provide SSO, but don’t require users to be automatically redirected to SSO, I also recommend to use the login.html and add a button to navigate the SSO sign-in endpoint and optionally hide the input boxes for username and password. This could be inconvenient because of the extra click needed.</li><li>If you (only) provide SSO and logging in should happen automatically, i.e. the SSO page being the landing page, I recommend creating or updating the index.html file in the theme/web folder as described in next section. This is the most convenient way for users as it does not require extra clicks.</li></ul><p><strong>How to configure your login flow</strong></p><p>If you run your application locally, the deployment folder contains a web folder, showing all the static files which are served. It also includes an <em>index-example.html</em> file, which you can use as a base for your custom <em>index.html</em> file.</p><p>If your <em>index.html</em> has a script referring to DojoConfig, you are using an older (Dojo) framework. If possible, I would recommend switching to the Migration mode for React client, such that migrating to React in the future doesn’t require you to change the index file again. If it has script referring to <em>dist/index.js</em>, you are running the latest (React) framework. Both scripts are very similar and interchangeable in most cases as long as you don’t run the application on a sub path, i.e. domain/my-app.</p><p>Copy this <em>index-example.html</em> file to the theme/web folder and we can start the adjustments to our needs. We can start by setting a custom HTML title and the main language of your app. To manipulate the login flow, we need to adjust the script where the originURI cookie is set, which looks like this:</p><pre>&lt;script&gt;  <br>if (!document.cookie || !document.cookie.match(/(^|;) *originURI=/gi)) {<br>    const url = new URL(window.location.href);<br>    const subPath = url.pathname.substring(0, url.pathname.lastIndexOf(&quot;/&quot;));<br>    document.cookie = `originURI=${subPath}/login.html${window.location.protocol === &quot;https:&quot; ? &quot;;SameSite=None;Secure&quot; : &quot;&quot;}`;<br>}<br>&lt;/script&gt;</pre><p>The first line is to prevent overwriting the login landing page, which is only of interest if you have multiple ways of logging in. My advice: drop it, as otherwise your changes won’t reflect after deployment until the existing cookie is expired or removed. If you do want to serve the <em>login.html</em> page for some environments without SSO setup, for example localhost, you could add a check based on the URL, but I rather advise to have SSO configured for running all environments. Anyways, you can change it to something like this to have a readable, functional, and safe implementation to redirect directly to your SSO login page:</p><pre>&lt;script&gt;  <br>  const url = new URL(window.location.href);<br>  const subPath = url.pathname.substring(0, url.pathname.lastIndexOf(&quot;/&quot;));<br>  let newCookie = `originURI=${subPath}`<br>  newCookie += &quot;/oauth/v2/login&quot; //JF26: this triggers the login procedure of the OIDC module, may require update for your module of single signon<br>  if (window.location.hash != &quot;&quot;) {<br>    newCookie += &quot;?cont=&quot;+window.location.hash.replace(&quot;#&quot;, &quot;p&quot;); //JF26: this stores the requested deeplink for the OIDC module, may require update for your module of single signon<br>  }<br>  if (window.location.protocol === &quot;https:&quot;)<br>    newCookie += &quot;;SameSite=None;Secure&quot;;<br>  document.cookie = newCookie;<br>&lt;/script&gt;</pre><p>Once you’re ready, double check if you stored the file in the app-main/theme/web folder, and the file contains substitution variables like {{unsupportedbrowser}} and {{cachebust}} to confirm it’s correctly copied from <em>index-example.html</em>. Now you’re ready to go!</p><p><strong>Security to the max</strong></p><p>If you want to maximize security, you want to avoid inline JavaScript and enforce this via the Content Security Policy (CSP) header. In that case you can put the script in a separate file referred to by the <em>index.html</em> file, such that you can configure the CSP HTTP Header without allowing unsafe-inline scripts. Or if you can manipulate the HTTP Headers via the infrastructure: let the webserver (NGINX for example) add the login page as Set-Cookie header.</p><p><strong>When deviation from the happy flow occurs</strong></p><p>Depending on which SSO module and Identity Provider you use, the authentication or authorization can fail. Don’t forget to test these situations and provide clear instructions on error pages how users can address their issues. You can for example create a static <em>unauthorized.html</em> file and redirect users in the SSO module when they are successfully authenticated, but don’t have the right authorization.</p><p><strong>Conclusion</strong></p><p>In this blog post I showed the security impact of the Anonymous Users feature in Mendix apps. Hopefully I convinced you that it is one of the 3 important lines of defence in your Mendix application Security and should be disabled whenever possible. I was happy to share how the login process works and hopefully empowered you to develop your own <em>index.html</em> to redirect users to your SSO provider. Let me know in the comments whether you are able to implement it!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=45aebdd10615" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Write-up Mendix CTF 2025: Patient Exporter I]]></title>
            <link>https://medium.com/@johan.flikweert/write-up-mendix-ctf-2025-patient-exporter-i-b28c73fa6e0d?source=rss-a98e8e51ba32------2</link>
            <guid isPermaLink="false">https://medium.com/p/b28c73fa6e0d</guid>
            <category><![CDATA[ctf-writeup]]></category>
            <category><![CDATA[mendix]]></category>
            <dc:creator><![CDATA[Johan Flikweert]]></dc:creator>
            <pubDate>Thu, 11 Dec 2025 09:55:12 GMT</pubDate>
            <atom:updated>2025-12-11T09:55:12.023Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/623/1*L_Avcw4ThEGnOHZzIn1_DQ.png" /></figure><p><strong>Vulnerability</strong>: TSU-07: Insecure custom authentication<br><strong>Application</strong>: <a href="https://patientportal2025.mendixctf.com/">https://patientportal2025.mendixctf.com/</a><br><strong>Reality check:</strong> Developing custom authentication is like building your own house: doable if you’re equiped for it, otherwise, use prefab or let others do it. Both straw houses and well build prefab houses can be found in the marketplace, make sure you use the thoroughly tested modules!</p><p>This challenge provides an module mpk and an example of an expired access token. This looks like some broken JWT…</p><pre>An expired token for Patient Exporter I<br>eyJwaWQiOiAiUDAwNDIiLCJleHBpcmF0aW9uIjogMTc1NzQyMTQ0OTExN30=.9o16oqcvWYatAwLF5gf0dsPW3hke4fAC6mH/G9IBwKE=</pre><p>Let’s first inspect this by base64 decoding it 🕵️:</p><pre>{&quot;pid&quot;: &quot;P0042&quot;,&quot;expiration&quot;: 1757421449117}</pre><p>Alright, that’s something, but indeed no JWT. And that second part is binary, it doesn’t give readable text, so is probably some signature. Now let’s continue the investigation loading the module in an app, hopefully without too much errors! 🫣</p><p>In the module, we see 3 folders: ‘Authentication’ and ‘Operations’ contain logic and REST, containing a published REST service. Quick check the only operation served should be on <a href="https://patientportal2025.mendixctf.com/rest/patientexport/v1/patient/export">https://patientportal2025.mendixctf.com/rest/patientexport/v1/patient/export</a> ✅ Check, it gives a 401 in my browser. So if we manage to supply some valid authentication, we hopefully can use Postman to export a flag ehh, eleet patient using the query parameter patient=31337. 😁</p><p>Let’s fix the 12 errors by creating a Portal module with Partner and Patient entities and importing the CommunityCommons module from the marketplace. ⌛ Alright, that’s fixed!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/436/1*aJQK3VXWZ3--bb6OcxN0gA.png" /></figure><p>Now focus on the SUB_ValidateAuthToken flow, as that’s the authentication flow selected in the Published REST Service 🔎</p><p>The token is extracted from the HTTP Header, the payload is extracted from the token, the expiry is checked to be in the future, a PartnerKey is retrieved from database by a provided PartnerId and then the SharedKey is used to generate a signature for the payload, which then is checked to equal the provided signature. Then the linked Partner is returned as user to execute the operation. Seems all 200 OK 🟢.</p><p>Ow, wait: the match of signatures is not done by checking if the strings are equal, but with a isMatch function, and we know that that’s an regex function. Would that be exploitable? 🤩</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/546/1*Ngsmzo5Gj3YJk6uuPDHD_Q.png" /></figure><p>Yeah, it looks like it: the signature provided by the API user is used as the regular expression! So let’s try to construct a token with .* as signature to always have a match. 🕺 As payload we use PartnerId P0042, as we know this is used before and some expiry date in the future which uses apparently the milliseconds notation:</p><pre>{&quot;pid&quot;: &quot;P0042&quot;,&quot;expiration&quot;: 1767169361000} </pre><p>Basing this 64 together, we’re gonna try:</p><pre>eyJwaWQiOiAiUDAwNDIiLCJleHBpcmF0aW9uIjogMTc2NzE2OTM2MTAwMH0=..*</pre><p>And there we have another flag! 🚩 <em>Gooooo Valcon Hackers </em>🕵️</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/542/1*EtSsTMB6y8cVWZn3cVuSnA.png" /></figure><p>As mentioned in the reality check: whenever possible, use hardened modules for authentication, like OIDC and SAML, as these are pentested regularly. As soon as the bug of the provided challenge is exposed, it’s a security thread, so one could also argue that keeping your authentication logic should be kept secret, but maintaining these custom authentication modules for every app separately is definitly something you don’t want without using the (private) marketplace. Make sure you let this review thoroughly by a knowledgeable colleague or via a pentest! 🎯</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b28c73fa6e0d" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Write-up Mendix CTF 2025: Refill Overkill]]></title>
            <link>https://medium.com/@johan.flikweert/write-up-mendix-ctf-2025-refill-overkill-f20ef2b0a327?source=rss-a98e8e51ba32------2</link>
            <guid isPermaLink="false">https://medium.com/p/f20ef2b0a327</guid>
            <category><![CDATA[mendix]]></category>
            <category><![CDATA[ctf-writeup]]></category>
            <dc:creator><![CDATA[Johan Flikweert]]></dc:creator>
            <pubDate>Thu, 20 Nov 2025 12:54:15 GMT</pubDate>
            <atom:updated>2025-11-20T12:54:15.971Z</atom:updated>
            <content:encoded><![CDATA[<p>We were able to solve this challenge after a little hint we got just 15 minutes before the CTF ended. After revisiting the steps we took earlier, we were able to solve it just in time! Let me walk you through ‘Refill Overkill’! 🕵️</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/625/1*RNIqFQMG9UCylpnAaeb1qQ.png" /></figure><p><strong>Vulnerability</strong>: TSU-03: Insecure microflows<br><strong>Application</strong>: <a href="https://patientportal2025.mendixctf.com/">https://patientportal2025.mendixctf.com/</a><br><strong>Reality check:</strong> Too often, minimal or no validation is applied in the ‘actual’ microflow, this has a very high likelihood in production environments.</p><p>The challenge text clearly steers us towards the “My Prescriptions” page where we can order drugs we have prescriptions for. Opening the page for the ‘Obecalb’ drug gives a pop-up to edit update the ‘Preferred Dose’ value and the ‘Next fill’ date:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/759/1*HTObBObXGObzexX5PDccYQ.png" /></figure><p>As always, let’s do some exploratory testing and try what happens if you enter some values and dates. Just starting with an empty date, let’s try varying the dose: 💊</p><ul><li>More than maximum: before I can hit the button, an on change event has reset the dose to the previous value</li><li>Normal, exactly the maximum, negative and zero values: seems to be processed normally, an info message confirms ‘Your prescription has been updated and can be retrieved from the chosen date.’</li><li>Empty: results into an technical error (we found a bug, but it doens’t do much more)</li></ul><p>Also playing with the date doens’t get us some ‘Obecalp’ sooner: 🗓️</p><ul><li>Entering a date before 14 days in the future: before I can hit the button, an on change event has reset the date to the previous value</li><li>Empty and any date in 2 weeks: seems to be processed normally</li></ul><p>So what would be the goal? We see that ‘Obecalp’ is the reverse of placebo, so maybe we want a more effective drug instead? 🤷‍♂ ️Is the read-only display maybe just front-end? Let’s see if we get the readonly in our network or if we can mess around with it. ⌨️</p><p>When inspecting the network traffic, we see a lot of different operationId’s:</p><ul><li>cSTZ… is the data source microflow for My Prescriptions</li><li>WNQ1… is the validation microflow for the Preferred Dose</li><li>HQpl… is the validation microflow for the Refill Date</li><li>Cm//… is the ‘Fill prescription’ button</li><li>ifrO… is the Cancel button (same as close pop-up)</li></ul><p>Looking at the response of the data source microflow: the association is not marked as readonly, only the Code of the referred drug, right?</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/358/1*L87t_653R5F5QYHaA-u_aA.png" /></figure><p>Unfortunately, this was a waste of time, because when you properly get all the attributes using mx.data.get, the association to the drug is readonly after all. 😐</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/672/1*7LbbPupcUFErupAVJfRiDg.png" /></figure><p>It’s time to put Burp into action and let’s start by intercepting the traffic. Maybe we can drop the validation on Dose, as this 13.36 amount is screaming to be overfilled. Let’s try to make this 13.37, as this should leet to something! 🛎️🛎️🛎️️️</p><p>OK: intercepted the call to WNQ1, dropped it and clicked away the error, now let’s try to order! Technically, this will result in a call to CM// including a part with ‘changes’ in the request.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/632/1*yVJoy6MZc9JvWpD1zUaX0A.png" /></figure><p>Ah no, it’s still validated! We found out that the order flow also validates the dose, how can be bypass that? During the CTF, I did some digging around the Next Fill date, but without any luck. The hint we finally got during the CTF is to look again to all the operations. So we took a better look at the untouched 2 operations: cancel and validation, there must be something there! 🕵️</p><p>Let’s redo what we just did: drop the call triggering the dose validation and then trigger the other operations, first the Cancel: maybe it doesn’t really cancel?</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/877/1*rpj66nV15QiVGhRhXilm6Q.png" /></figure><p>No, it really resets the Dose value, back to it’s committed value.</p><p>The validation of the Date? How would that work? Anyways, again: drop the call triggering the dose validation and then trigger the date validation. Hey, that looks nice: our 13.37 seems committed, maybe that helps?! 🤩</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/864/1*P3saRnbZyrIvruSzy4DXCA.png" /></figure><p>Can we order this prescription? Yes, we can and we find the flag! 🚩<em>Gooooo Valcon Hackers </em>🕵️</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/628/1*49b--WN-3ebBdObLH1Qsbg.png" /></figure><p>So what was happening? Why did the validation flow only trigger the first time and not the second? 🤔 The only difference was that the changes were committed, so apparently the Order flow only executed the validation when the attribute was changed. It seems ok to only validate changed attributes, but as it turns out: it may be unsafe! ⚠️</p><p>How to prevent situations like this in your development:</p><ul><li>Always validate input for flows which can be called by the client, just reuse the (partial) validation flows triggered in on change flows.</li><li>Don’t assume validations have taken place earlier in the process, preferably do double validations then the possibility to bypass them!</li><li>If possible, avoid committing the full object after partial validation.</li></ul><p>For us, this was quite a challenge! Did you manage to solve it? Don’t forget to check if your validations cannot be bypassed! 💡</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f20ef2b0a327" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Write-up Mendix CTF 2025: Site Unseen]]></title>
            <link>https://medium.com/@johan.flikweert/write-up-mendix-ctf-2025-site-unseen-968fa64ee25a?source=rss-a98e8e51ba32------2</link>
            <guid isPermaLink="false">https://medium.com/p/968fa64ee25a</guid>
            <category><![CDATA[ctf-writeup]]></category>
            <category><![CDATA[mendix]]></category>
            <dc:creator><![CDATA[Johan Flikweert]]></dc:creator>
            <pubDate>Thu, 06 Nov 2025 13:36:47 GMT</pubDate>
            <atom:updated>2025-11-06T13:36:47.282Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/624/1*tUcGhQvAa0eLsNYeavf0jQ.png" /></figure><p><strong>Vulnerability</strong>: TSU-09: Insecure UI Components<br><strong>Application</strong>: <a href="https://patientportal2025.mendixctf.com/">https://patientportal2025.mendixctf.com/</a><br><strong>Reality check:</strong> Unused microflows which are (still) available for users is very common and developers should definitely check this regurlarly.</p><p>As we saw in the <a href="https://medium.com/@johan.flikweert/write-up-mendix-ctf-2025-fiscal-life-support-tier-2-87a67a8db997">Fiscal Life Support challenge</a>, the first /xas/ request is to get_session_data and this returns a lot of valuable data. Let’s inspect the microflows hidden in there! 🕵️</p><blockquote>For applications without anonymous users (I always recommend to disable anonymous users whenever possible), the first /xas/ request returns a 401 Unauthorized response. This 401 is handled to navigate the user to the originURI, a cookie telling where to login, by default at /login.html.<br>Just after succesful login the get_session_data will again be fired towards /xas/ returning the valuable data for your role(s).</blockquote><p>In our case the application allows anonymous users, so the /xas/ response returns the session data, and scanning through that huge response, we find from line 2167 a list of microflows in a special format: 📃</p><pre>    &quot; microflows &quot;: {<br>        &quot;{\&quot;a\&quot;:[],\&quot;p\&quot;:[\&quot;Portal.Doctor\&quot;]}&quot;: &quot;RLV22MuV1lKyNXb6P7skxA&quot;,<br>        &quot;{\&quot;a\&quot;:[[\&quot;Portal.Prescription_Drug\&quot;,\&quot;Portal.Prescription\&quot;],[\&quot;Portal.Prescription_Patient\&quot;,\&quot;Portal.Patient\&quot;],[\&quot;Portal.Prescription_Patient\&quot;,\&quot;Portal.Prescription\&quot;],[\&quot;Portal.Prescription_Drug\&quot;,\&quot;Portal.Drug\&quot;]],\&quot;p\&quot;:[\&quot;Portal.Prescription\&quot;]}&quot;: &quot;WNQ1syd+uViq0xVlNY1LaQ&quot;,<br>        &quot;{\&quot;a\&quot;:[],\&quot;p\&quot;:[\&quot;Administration.Account\&quot;]}&quot;: &quot;GuVxpf6dSleq/eoTRqzDWQ&quot;,<br>        &quot;{\&quot;a\&quot;:[],\&quot;p\&quot;:[\&quot;System.FileDocument\&quot;]}&quot;: &quot;lzxQG0GrbluQH/8ctDV9Bg&quot;,<br>        &quot;{\&quot;a\&quot;:[[\&quot;Portal.Prescription_Patient\&quot;,\&quot;Portal.Patient\&quot;],[\&quot;Portal.Prescription_Patient\&quot;,\&quot;Portal.Prescription\&quot;],[\&quot;Portal.Prescription_Drug\&quot;,\&quot;Portal.Drug\&quot;],[\&quot;Portal.Prescription_Drug\&quot;,\&quot;Portal.Prescription\&quot;]],\&quot;p\&quot;:[\&quot;Portal.Prescription\&quot;]}&quot;: &quot;HQpl+c5/ZV6mNfULUmJzRg,Cm//EjKFy1ebyWMUgehf4w&quot;,<br>        &quot;{\&quot;a\&quot;:[],\&quot;p\&quot;:[\&quot;Portal.Document\&quot;]}&quot;: &quot;wQuDuG4/N1uvnet+70S7hw&quot;,<br>        &quot;{\&quot;a\&quot;:[],\&quot;p\&quot;:[]}&quot;: &quot;sEmr/aO5tlm4cS8t54h6Eg,mipcOCQUxVeI3F4I20YtCA,7Xpa+yKV+1e1H+TF38wilQ,iANWNVJEcl6jvPDhzmWNKQ,WvzxrypJTFqEvTpiuPFUXA&quot;,<br>        &quot;{\&quot;a\&quot;:[[\&quot;Administration.AccountPasswordData_Account\&quot;,\&quot;Administration.AccountPasswordData\&quot;],[\&quot;Administration.AccountPasswordData_Account\&quot;,\&quot;Administration.Account\&quot;]],\&quot;p\&quot;:[\&quot;Administration.AccountPasswordData\&quot;]}&quot;: &quot;mt0bQnybUFSUB9Dqc5JjvA&quot;,<br>        &quot;{\&quot;a\&quot;:null,\&quot;p\&quot;:[]}&quot;: &quot;2PIVCck2kFyTQCZb7aPkTQ&quot;<br>    },</pre><p>The “attributes” of this object are JSONs representing the associations (a) and parameters (p) of the microflow and the “values” represent the scrambled microflow names, comma separated if there are multiple. So the first line (starting with RL) takes a Doctor as input parameter when called, while the last line (2P) doesn’t take any parameters. Also the 2-but-last line is without parameters, and contains 5 more microflows: sE, mi, 7X, iA and Wv. Let’s try to call them! 🤙</p><blockquote>If you want to see how the Mendix runtime server translates requests with an operation id into functional operations: check the deployment folder (or unzip a deployment package) and navigate to the model folder, in which you will find operation.json. This file reveils the translation of an operationId to a readable microflow name, XPath query or something else. 💡</blockquote><p>Now another challenge comes into play: we would like to manipulate the request to the server, as the javascript interface is not very convenient. Here Burp Suite saves the day, as this tool enables you to intercept requests and manipulate them before actually send to the server.</p><p>As we click the button to open the homepage, we can intercept:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/725/1*mlzDB4k5xXZQ7-6lakghzQ.png" /></figure><p>And we recognize this request (sE), and manipulate this to the next on ein the list (mi) to try it out. After clicking away some error, we get the pop-up about some test admin account, this goes smooth! 🥳</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*1EylVfIUpYItyWX6Yx8rDw.png" /></figure><p>Not sure if we should do something with the pop-up, but let’s base64 decode what’s there.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/434/1*idTMBy0adRMabZ6kWARWvg.png" /></figure><p><em>Gooooo Valcon Hackers </em>🕵️We found another flag! 🚩</p><p>The amount of solves surprised me a bit, probably because using Burp is more common practice for the CTF visitor than using mx.data.get? 🤷‍♂️</p><p>As accessing random microflows is definitely something you want to protect, how does Mendix handle this? A microflow ends up in the session data when the following two conditions are met:</p><ol><li>The logged in user has access (via the module roles configured).</li><li>The microflow is used on at least one place, regardless if that page is used and which module roles are linked to that page.</li></ol><p>Did you know this? For me this was really an eyeopener, and I think page access should be handled stricter by Mendix to also tighten this better.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=968fa64ee25a" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Write-up Mendix CTF 2025: Restless Hackers (Tier 2)]]></title>
            <link>https://medium.com/@johan.flikweert/write-up-mendix-ctf-2025-restless-hackers-tier-2-d0301cd91186?source=rss-a98e8e51ba32------2</link>
            <guid isPermaLink="false">https://medium.com/p/d0301cd91186</guid>
            <category><![CDATA[ctf-writeup]]></category>
            <category><![CDATA[mendix]]></category>
            <dc:creator><![CDATA[Johan Flikweert]]></dc:creator>
            <pubDate>Fri, 17 Oct 2025 13:16:57 GMT</pubDate>
            <atom:updated>2025-10-17T13:16:57.631Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/627/1*00Vt0kA270zW6xFuwmsymw.png" /></figure><p><strong>Vulnerability</strong>: TSU-02: Insecure entity access<br><strong>Application</strong>: <a href="https://patientportal2025.mendixctf.com/">https://patientportal2025.mendixctf.com/</a><br><strong>Reality check:</strong> It’s very common that entity access is not as tight as desired. Also for non-persistent objects it’s important to apply zero trust.</p><p>For this flag, we didn’t need extra sleep as we solved within 2 hours after the CTF started. The /rest-doc/ endpoint is not exposed, so we definitely should load the provided mpk-module in Mendix.</p><p>In the Published Rest Service (PRS), we see one operation is served without any authentication on /rest/pubsync/v1/synchronize?resource={resource}, which calls the microflow SUB_SynchronizePublicData. Well, opening this in the browser doesn’t reveil much, does it? 🤔</p><p>Let’s continue our investigation. The flow does another REST Call and that endpoint is secured, so maybe we need to breach that security somehow? Ahh, there is a FlagEndpoint, what happens if we call that?</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/589/1*WeIP5AS5G7xNjdyP4rzT-A.png" /></figure><p>OK, so we do need that session key! In SUB_GetSessionKey, we see that an API-key is exchanged for a SessionKey, can we do the same? Let’s call /rest/v1/auth/token using HTTP-Header X-API-Key: my_secret, as that is the default value. This worked during the first day of the CTF, but that route was not intended and cut off. So let’s continue on another route. 🕵️</p><p>SUB_GetSessionKey is only used in SUB_SynchronizePublicData, but wait: this flow is also used in the nanoflow ManualImport, used on the page ManualImport. Can we open the page, trigger the nanoflow and subsequently that microflow? Let’s try to open the page using mx.ui.openForm2(&quot;Importer.ManualImport&quot;, {}, null, null, { location: &quot;content&quot; }); and yes: click that button! 😃</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/438/1*kFDgWePsOoeYFya1-HibNw.png" /></figure><p>If we do this while monitoring the Network in our browser Dev Tools, we see that the raw response contains a lot of objects: mostly Importer.Result objects, but also the Importer.Token having the Sessionkey 🎁:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/487/1*6-d7o_L0jhfO0FxBtmXO9Q.png" /></figure><p>Let’s use that sessionkey as HTTP-Header in a similar format as the synchronization call towards the Flag Endpoint using Postman and see if that reveals the Flag! Yes, it does: we captured another flag! 🚩 <em>Gooooo Valcon Hackers </em>🕵️</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*s0rrsIRerXLmoZ8RLf9eBA.png" /></figure><p>So why does that Token object show up in your network? Only because you have read access for the Token entity, it’s returned in the response from the microflow to the nanoflow, even passed along from the subflow! This may seem insecure, but in this way allows Mendix you to update the client’s cache via microflows. Anyways, as developer: make sure ‘secret’ stuff is never exposed to users, even when it’s non-persistent! 🏁</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d0301cd91186" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Write-up Mendix CTF 2025: No Accessible Vector (Tier 2)]]></title>
            <link>https://medium.com/@johan.flikweert/write-up-mendix-ctf-2025-no-accessible-vector-tier-2-d2550a6d89a7?source=rss-a98e8e51ba32------2</link>
            <guid isPermaLink="false">https://medium.com/p/d2550a6d89a7</guid>
            <category><![CDATA[ctf-writeup]]></category>
            <category><![CDATA[mendix]]></category>
            <dc:creator><![CDATA[Johan Flikweert]]></dc:creator>
            <pubDate>Fri, 17 Oct 2025 07:13:18 GMT</pubDate>
            <atom:updated>2025-10-17T07:13:18.519Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/627/1*fF2b6sQJ83xAxmE7FYWZ2Q.png" /></figure><p><strong>Vulnerability</strong>: TSU-09: Insecure UI Component<br><strong>Application</strong>: <a href="https://patientportal2025.mendixctf.com/">https://patientportal2025.mendixctf.com/</a><br><strong>Reality check:</strong> Unintended ability to show pages is very common with Mendix unfortunately, but usualy static pages don’t disclose secrets, data does.</p><p>From previous CTFs we know that pages and layouts are (were) a static XML file, and conditional visibility is handled front-end. However, with strict mode everything is a bit scrambled and broken into multiple pieces, making it somewhat more challenging. The initial load of the home page results into ~30 requests and no XML files anymore:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1008/1*n3Brx5CvQZ1_HLPAap7xpQ.png" /></figure><p>Portal.Home_Web is aparently the main page served, but now as static javascript file. Good to know: references to pages still use the .page.xml extension, so when we search for .page.xml in any document we find a lot of references to pages in those javascript files:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/975/1*CV1glhIRUOLG1z8LRllOoQ.png" /></figure><p>We recognize some of the menu items (Home, Documents, MyDoctors, MyAppointments, MyPrescriptions) and also see 2 items which are hidden items for us as User:</p><ul><li>Administration/Account_Overview.page.xml for Administrator</li><li>Portal/DevTools.page.xml for Tester</li></ul><p>The latter asks for some further inspection, don’t you think? 🕵️</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/431/1*fL4VXE6RHSFGFnuinR4jkQ.png" /></figure><p>In strict mode, the ‘old’ mx.ui.openForm() function doesn’t work anymore . It is replaced by mx.ui.openForm2(). However, I did not find any documentation on that function, so I investigated before the CTF event how this function can be used. With some help of ChatGPT deducting the input parameters, I unravelled a way to show pages just like we did before strict mode: 😎</p><pre>mx.ui.openForm2(&quot;Portal/DevTools&quot;, {}, null, null, { location: &quot;content&quot; });</pre><p>And applying this during the CTF showed the page and revealed the flag, another flag captured! 🚩 <em>Gooooo Valcon Hackers </em>🕵️</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*6VANctEEqYUmrlJ-y7oRNw.png" /></figure><p>Having only 21 solves on this challenge shows that this code snippet to open pages is not common knowledge, so store it in your toolbox! This enables you to check your page security and will reveal which queries are executed on that page. Off course, the data should be correctly restricted using entity access to prevent situations like this.</p><p>To prevent usage of openForm2 function, one should hide sensitive page names completely from regular users or anonymous users. This could be achieved using microflows having the ‘Open page’ activity and configure the allowed roles to call those microflows accordingly. Don’t forget to choose an unguessable page name as well. 💡</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d2550a6d89a7" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Write-up Mendix CTF 2025: Mix-up]]></title>
            <link>https://medium.com/@johan.flikweert/write-up-mendix-ctf-2025-site-unseen-tier-2-a2e1cb16d6c4?source=rss-a98e8e51ba32------2</link>
            <guid isPermaLink="false">https://medium.com/p/a2e1cb16d6c4</guid>
            <category><![CDATA[ctf-writeup]]></category>
            <category><![CDATA[mendix]]></category>
            <dc:creator><![CDATA[Johan Flikweert]]></dc:creator>
            <pubDate>Thu, 16 Oct 2025 18:57:41 GMT</pubDate>
            <atom:updated>2025-11-07T08:06:46.082Z</atom:updated>
            <content:encoded><![CDATA[<p>Sorry, I mixed up 2 challenges: <em>Site unseen</em> and <em>No Accessible Vector</em>. Click the links below to read those write-ups.</p><ul><li><a href="https://medium.com/@johan.flikweert/write-up-mendix-ctf-2025-no-accessible-vector-tier-2-d2550a6d89a7">Write-up Mendix CTF 2025: No Accessible Vector (Tier 2)</a></li><li><a href="https://medium.com/@johan.flikweert/write-up-mendix-ctf-2025-site-unseen-968fa64ee25a">Write-up Mendix CTF 2025: Site Unseen</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a2e1cb16d6c4" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Write-up Mendix CTF 2025: Fiscal Life Support (Tier 2)]]></title>
            <link>https://medium.com/@johan.flikweert/write-up-mendix-ctf-2025-fiscal-life-support-tier-2-87a67a8db997?source=rss-a98e8e51ba32------2</link>
            <guid isPermaLink="false">https://medium.com/p/87a67a8db997</guid>
            <category><![CDATA[mendix]]></category>
            <category><![CDATA[ctf-writeup]]></category>
            <dc:creator><![CDATA[Johan Flikweert]]></dc:creator>
            <pubDate>Wed, 15 Oct 2025 20:17:49 GMT</pubDate>
            <atom:updated>2025-10-17T06:31:29.699Z</atom:updated>
            <content:encoded><![CDATA[<p>Mendix Capture The Flag had a lot of nice challenges this year, and as expected: it had an application with strict mode enabled! Strict mode is a security setting, which becomes available when you enable React Client in the app settings. This mode disables the functionality to query arbitrary data using XPath, which makes hacking definitely different than before. Whether it’s also more secure is up for debate, probably I will share some of my thoughts on that during my write-ups. Now, let’s capture this flag! 🚩</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/623/1*Ld2Ob2ApQl2VvROrKZF69A.png" /></figure><p><strong>Vulnerability</strong>: TSU-02: Insecure entity access<br><strong>Application</strong>: <a href="https://patientportal2025.mendixctf.com/">https://patientportal2025.mendixctf.com/</a><br><strong>Reality check:</strong> It’s very common that entity access is not as tight as desired. This can be prevented by periodically reviewing the security overview within Studio Pro (<a href="https://docs.mendix.com/refguide/security-overview/">https://docs.mendix.com/refguide/security-overview/</a>).</p><p>This first challenge explicitly directs us in finding financial details about the budget, a clear clue what to look for. 🕵️ In the first request to /xas/, the session is initialized, and the response contains next to the real session data (User info and CSRF-token) also values of enumerations and constants, system texts and locale information. But most importantly, it contains the metamodel (entities) and some objects. Hopefully we find something useful in this 42 kilobytes of information 😉.</p><p>In the list of objects, we find a patient, an appointment, a document, a message, another appointment and a prescription, all from the Portal module. In the total list of 43 entities, we focus on the Portal module and find the existence of the following 12 entitities are probably of interest:</p><pre> Line  156:             &quot; objectType &quot;: &quot; Portal.SessionSettings &quot;,<br> Line  190:             &quot; objectType &quot;: &quot; Portal.Drug &quot;,<br> Line  297:             &quot; objectType &quot;: &quot; Portal.Doctor &quot;,<br> Line  356:             &quot; objectType &quot;: &quot; Portal.Ban &quot;,<br> Line  562:             &quot; objectType &quot;: &quot; Portal.Hospital &quot;,<br> Line  885:             &quot; objectType &quot;: &quot; Portal.Document &quot;,<br> Line 1154:             &quot; objectType &quot;: &quot; Portal.Appointment &quot;,<br> Line 1247:             &quot; objectType &quot;: &quot; Portal.Message &quot;,<br> Line 1566:             &quot; objectType &quot;: &quot; Portal.Patient &quot;,<br> Line 1784:             &quot; objectType &quot;: &quot; Portal.Partner &quot;,<br> Line 1969:             &quot; objectType &quot;: &quot; Portal.Prescription &quot;,<br> Line 2123:             &quot; objectType &quot;: &quot; Portal.Budget &quot;,</pre><p>As we look for the budget details, that Portal.Budget entity is definitely of interest, but we don’t have objects yet… 🤔<br>Browsing through the entities, we see an association from Hospital to Budget, from Doctor to Hospital and from both Message and Appointment to Doctor. Let’s check that out!</p><p>mx.data.get may be restricted for arbitrary XPaths, it’s still possible to retrieve objects by their ID, assuming we have read access at some of their properties. 💡<br>We have an ID for our Doctor using the objects Message and Appointment, so let’s try to retrieve all attributes we may read executing the following code in the console, resulting into:</p><pre>mx.data.get({<br>    guid: &quot;3659174699036655&quot;,<br>    callback: console.info<br>});</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/691/1*xjw9ruj6fCo0u6OYC95EkQ.png" /></figure><p>Opening the resulting Jg object, we see in _jsonData &gt; attributes a filled association to Hospital, and if we repeat this exercise for the Hospital id, we see a filled association to Budget. 🎉 We’re getting somewhere!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/762/1*1LoNajXO5-WypyOX0M9lMw.png" /></figure><p>Retrieving this Budget object, we don’t see any suspicious attributes, but we see it’s a File Document. So let’s download by putting <br>/file?guid=23643898043823266 in the URL.<br>And there we have it: we found the flag in the downloaded Excel file! 🚩<em>Gooooo Valcon Hackers </em>🕵️</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/819/1*9ThMuw0JcRu5XxjRcrAyzg.png" /></figure><p>If strict mode wasn&#39;t enabled, this flag was very easy to find using some developer tool. This stresses the importance of configuring entity access correctly.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=87a67a8db997" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Maintenance vs. Performance — Mendix Meetup Recap]]></title>
            <link>https://medium.com/@johan.flikweert/maintenance-vs-performance-mendix-meetup-recap-4809b9f40c1d?source=rss-a98e8e51ba32------2</link>
            <guid isPermaLink="false">https://medium.com/p/4809b9f40c1d</guid>
            <category><![CDATA[mendix]]></category>
            <category><![CDATA[meetup]]></category>
            <category><![CDATA[performance]]></category>
            <category><![CDATA[maintenance]]></category>
            <dc:creator><![CDATA[Johan Flikweert]]></dc:creator>
            <pubDate>Fri, 27 Jun 2025 13:08:05 GMT</pubDate>
            <atom:updated>2025-06-27T13:08:05.468Z</atom:updated>
            <content:encoded><![CDATA[<h3>Maintenance vs. Performance — Mendix Meetup Recap</h3><p>A couple of weeks ago, Valcon hosted a Mendix Meetup with an interactive quiz on Maintenance vs. Performance, where these developer profiles were characterized by <a href="https://medium.com/@johan.hamstra">Johan</a> vs. Johan (me). The idea was to determine your inner developer profile by asking questions on your development approach. For those who couldn’t join, let’s reflect on these questions and look at the given responses in this Mendix Meetup Recap. If you did join, you might want to revisit quesions 4 and 8, where the majority had an incorrect answer!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/459/1*D7XRDnv95JyrhtAqMH0PQQ.png" /></figure><h4>Question 1: Developer focus</h4><p>We kicked things off with a self-reflection: where do you think your primary development focus lies? Most questions throughout the quiz could earn you either Maintenance or Performance points — and sometimes neither if your answer was just plain wrong.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*hBGDCYQJ1Jaepol9gGFjDA.png" /></figure><p>Of the ~60 attendees, 52 participated in the quiz. The majority saw themselves as maintenance-oriented developers. But when <a href="https://mentimeter100-sandbox.mxapps.io/">the points were tallied</a>, the difference was surprisingly small: 29 leaned Maintenance, 22 towards Performance. So… do we really know ourselves?</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/957/1*jUpGPCadlp5qMHiDyINiTA.png" /></figure><h4>Question 4: Retrieve over association vs. database</h4><p>After warming up, things got serious. This question sparked one of the most surprising insights of the evening.</p><figure><img alt="Imagine you need to retrieve the project members from a project. 
 Which would be usually faster?
 
A) Retrieve the project members of project over association
B) Retrieve the project members of project from database
C) There is no significant performance difference" src="https://cdn-images-1.medium.com/max/931/1*ejWmKLFthkULmxQLWYMFiQ.png" /></figure><p>This is a real myth buster, as retrieving over association in the child direction (i.e. the originating entity is not the owner of the association) always performs a database retrieve and combines this with the cache (items in dirty state). However this merge is unmeasurable fast, making C the only right answer (performance points). Did you know this? Only 35% got this right!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*A0hCORQV4qJEOgwHZUv_lQ.png" /></figure><p>FYI: retrieving over association in the other direction may be faster, only if the owning object is in dirty state. However, being in dirty state, the functional difference (from database or over association) should determine your solution, not performance.</p><h4>Question 5: Find vs. retrieve in loop</h4><p>Next up: a classic performance-vs-maintainability trade-off in a data import flow. Imagine we regularly import a list of Phone Numbers and instead of just using the existing String attribute “Country prefix”, we want to associate it with the Country as entity. In the process flow, we update all the Phone Numbers by looping over all of them and committing the updated association in the end. Would you ‘Retrieve the country from the database in the loop’ or ‘Load all countries in a list before the loop, and find the country within the list in the loop’?</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/752/1*cGm6ire8kJW0zxWUPvOT-A.png" /></figure><p>A satisfying large group chose for the second option, which is indeed better performing without losing maintainability.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4MS4nXoJa5eNzow7aoMPNQ.png" /></figure><p>Based om some testing, we now apply as rule of thumb:</p><blockquote>For lists smaller than ~1000 items, the Find activity already outperforms multiple DB calls after just two iterations.</blockquote><p>Just be careful with large lists: memory usage and Find performance may become problematic depending on your data model.</p><h4>Question 8: Retrieve generalization or specialization?</h4><p>This one stumped more than a few people! Consider these 2 different retrieves:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/733/1*J6wVwzarDq0jhbTJBjfZXQ.png" /></figure><p>Which one is faster when retrieving from database? Retrieving a Vehicle or retrieving a Car being a specialization of Vehicle? When you have retrieved the Vehicle, you only can only use the generalization attributes, right?</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*QXhFQIUVPzIXXl-WE6sCfw.png" /></figure><p>Maybe this will surprise you, but retrieving the Car (answer B) is faster! We saw that any retrieve of a generalisation will also retrieve the specialization in an additional query. And if the specialization contains more layers or your results contains different specializations, this will even execute multiple queries. The fun side: executing a cast doesn’t trigger a database call!</p><p>If you directly query a specialization, the database retrieve is executed using a join on database level, resulting in a single query, making it faster.</p><h4>Question 10: Use a generalisation for master data</h4><p>Let’s say your app has over 30 master data entities, many with common behaviors like Activate/Deactivate and a sorting order for dropdowns. What’s your modeling strategy? Would you:</p><ul><li>A) Create a generalization of masterdata to take care of the requested functionality and create specializations for the masterdata items.</li><li>B) Create a unique masterdata item and create the requested functionality separately for the masterdata items.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/825/1*RvkoYAAjecXOMm5w09kxdQ.png" /></figure><p>The audience results were nearly split, with a slight preference for B.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*eWf_DBAxcjxCdFHIVI55ag.png" /></figure><p>Clearly, buttons like activate and deactivate can be easily reused, making option A better for maintenance. However, it has a slight performance impact and masterdata may be heavily used, making B a logical choice from performance perspective. As we as Johan and Johan still don’t agree on this one, we’re glad the audience doens’t either.</p><p>Thanks for joining us at this Mendix Meetup — whether in person or now via recap. We had a great time debating, myth-busting, and sometimes being proven wrong. See you next time!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4809b9f40c1d" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[3 reasons why using microflows as data source is a bad idea]]></title>
            <link>https://medium.com/@johan.flikweert/3-reasons-why-using-microflows-as-data-source-is-a-bad-idea-d32998b5fb4c?source=rss-a98e8e51ba32------2</link>
            <guid isPermaLink="false">https://medium.com/p/d32998b5fb4c</guid>
            <category><![CDATA[datasource]]></category>
            <category><![CDATA[microflow]]></category>
            <category><![CDATA[mendix]]></category>
            <category><![CDATA[performance]]></category>
            <dc:creator><![CDATA[Johan Flikweert]]></dc:creator>
            <pubDate>Tue, 18 Mar 2025 15:07:34 GMT</pubDate>
            <atom:updated>2025-03-18T15:07:34.731Z</atom:updated>
            <content:encoded><![CDATA[<p>Hi Mendix community! I’d like to share some advice with you and highlight some of the internal workings of Mendix in this blog. Using microflows as a data source can be useful for debugging, but can cause serious performance issues. More than once, I encountered slow loading pages, which I resolved by removing the microflow data source! Below are 3 reasons why the microflow data source impacts your performance and considerations to improve your work.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/902/1*OFaSiUIZBsyA7cj2YRev6Q.png" /></figure><h4>Pagination, sorting and filtering</h4><p>Datagrids are good at pagination, and only request data from the page currently shown when you retrieve from database. If you retrieve from microflow however, it’s retrieving all records. Having large datasets, this heavily impacts the performance and is the number 1 reason to not use microflows as data source. Once loaded, pagination, sorting and filtering happens client side and will be super fast. Depending on the size of your dataset this reason may apply.</p><p><em>For those thinking: wasn’t there some System.Paging entity to support server side pagination, sorting and filtering? Yes, that’s supported in the Datagrid 1, but not in Datagrid 2 anymore/yet. See </em><a href="https://docs.mendix.com/refguide/server-side-paging/">https://docs.mendix.com/refguide/server-side-paging/</a></p><h4>Restriction by access rules</h4><p>The end-result of a microflow is not exactly the same as what is shown front-end. Between the end activity and the response message to the client the restrictions from access rules are applied. Depending on the complexity of the access rules, this already has negative impact on the performance executing these on the server instead of on the database.</p><p>In addition – as ‘apply entity access’ is never switched on in my experience – the resulting database retrieve is probably too broad causing too much data to be transferred between database and Mendix server again impacting the performance.</p><h4>Retrieved members</h4><p>When a database retrieve is done ‘directly’ by the client, the query can be optimized by only retrieving the members (attributes and associations) shown to the user. This is the default optimizing behavior of Mendix:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/545/1*jd9R6JVXf_G_dlpfItlR7g.png" /></figure><p>Using a microflow or nanoflow as data source, you can still configure this option, but it will only affect the communication between client and server and this optimization is not propagated in the communication between server and database. This increases database load and communication overhead, negatively impacting performance.</p><h3>Exceptions</h3><p>Exceptions I can think of now:</p><ul><li>Small datasets, i.e. &lt;100 records in total</li><li>Specific retrieval of single item</li><li>Complex retrievals</li></ul><p>I’m curious to your exceptions, feel free to share in the comment section!</p><h3>Alternatives</h3><p>If you think the performance is indeed negatively impacted, but you do need a data source microflow, there are some alternatives you might want to consider:</p><ul><li>Apply entity access has 2 benefits: it limits the size of the dataset retrieved and it doesn’t retrieve members that aren’t accessible, e.g. associations without read access.</li><li>Implementing your custom server side filtering using a containing data view.</li><li>And also a view entity may help to optimize your query. If someone does have access to attributes, applying entity access does very little restraining. Having a view entity allows to specifically retrieve the attributes you want to show.</li></ul><h3>Conclusion</h3><p>Microflows as a data source often lead to performance bottlenecks due to three key reasons:</p><ul><li><strong>Pagination, Sorting, and Filtering</strong> — A microflow retrieves all records at once, whereas a database retrieve only fetches the necessary data for the current page. This can severely impact performance, especially for large datasets.</li><li><strong>Access Rules Processing</strong> — Entity access rules are applied after a microflow executes, which may result in unnecessary data retrieval and extra server-side filtering, increasing load on the Mendix runtime.</li><li><strong>Inefficient Data Retrieval</strong> — When retrieving directly from the database, Mendix optimizes queries to fetch only required attributes. Microflows bypass this optimization, leading to higher database load and increased network traffic.</li></ul><p>Before implementing a microflow as a data source, ask yourself:<br> ✅ Can I retrieve data directly from the database instead?<br> ✅ Is my dataset small enough (&lt;100 records) to justify a microflow?<br> ✅ Are there complex retrieval requirements that truly require a microflow?</p><p>If a microflow is necessary, consider:<br> 🔹 Applying entity access to limit data retrieval.<br> 🔹 Using a view entity to optimize queries.<br> 🔹 Implementing server-side filtering instead of fetching unnecessary records.</p><p><strong>Final tip:</strong> Always document your decision in an annotation to help future developers understand why a microflow was used!</p><p>Have you encountered performance issues using microflows as a data source? Share your experiences in the comments!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d32998b5fb4c" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>