<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Akeem O. Salau</title>
    <description>The latest articles on DEV Community by Akeem O. Salau (@obrainwave).</description>
    <link>https://dev.to/obrainwave</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3960157%2F7f891c66-c2e4-4aa7-b153-dac6e54fc06e.jpg</url>
      <title>DEV Community: Akeem O. Salau</title>
      <link>https://dev.to/obrainwave</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/obrainwave"/>
    <language>en</language>
    <item>
      <title>The Midnight Call That Exposed a Silent Killer in a Laravel Store</title>
      <dc:creator>Akeem O. Salau</dc:creator>
      <pubDate>Thu, 09 Jul 2026 23:51:31 +0000</pubDate>
      <link>https://dev.to/obrainwave/the-midnight-call-that-exposed-a-silent-killer-in-a-laravel-store-334</link>
      <guid>https://dev.to/obrainwave/the-midnight-call-that-exposed-a-silent-killer-in-a-laravel-store-334</guid>
      <description>&lt;p&gt;My phone buzzed at 2:14 in the morning. I did not recognize the number, and I almost let it ring out, until I remembered that a friend had passed my contact to a store owner earlier that day, telling her to call me directly if things got worse overnight. It clearly had.&lt;/p&gt;

&lt;p&gt;Her voice was shaking, the voice of someone who does not normally call a stranger past midnight. I could tell right away this was not a small problem.&lt;/p&gt;

&lt;p&gt;"Orders have stopped coming in," she said. "I do not know what is happening. I thought maybe it was just a slow night, but it has been hours."&lt;/p&gt;

&lt;p&gt;She told me she had noticed the order count on her dashboard looked unusually quiet earlier that evening, but she brushed it off, thinking people were simply not shopping that night. It was only when she got curious and tried to open the products page herself that she realized something was badly broken. The page spun. And spun. She waited. Almost five minutes later, it finally loaded.&lt;/p&gt;

&lt;p&gt;Five minutes. On a products page. For a store that normally loaded in under two seconds.&lt;/p&gt;

&lt;p&gt;I told her to give me fifteen minutes and hung up, already reaching for my laptop before the call had even fully ended.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Five Minutes Feels Like Forever
&lt;/h2&gt;

&lt;p&gt;If you have ever run an online store, you know that every second a page takes to load is a second closer to losing a customer. Studies on ecommerce behavior have shown for years that shoppers abandon pages that take more than a few seconds to load. A five minute load time is not slow. It is effectively down. To a customer scrolling on their phone during a lunch break or a late night browsing session, a page that will not load is a page that does not exist.&lt;/p&gt;

&lt;p&gt;And for her, this was not a small inconvenience. This was her income. Every minute that products page stayed broken was a minute her store was invisible to real buyers, even though the site was technically still online.&lt;/p&gt;

&lt;p&gt;She sent over temporary server access within minutes, no questions asked, she just wanted it fixed. I logged in for the first time ever on this codebase, and since I had no history with it and no time to read through it line by line, I dropped in Laravel Telescope right there on the spot to get real visibility into what was actually happening under the hood. Within seconds of loading the products page myself, I understood why she had been sitting there watching a spinner for five minutes.&lt;/p&gt;

&lt;p&gt;The query log was flooded. Not with ten queries. Not with fifty. Thousands of queries were firing for a single page load.&lt;/p&gt;

&lt;h2&gt;
  
  
  Digging Into the Code
&lt;/h2&gt;

&lt;p&gt;My first instinct was that maybe the server had run out of memory, or a background job had somehow locked up the database, or there had been a bad deployment. But the query count told a different story. This was not a server problem. This was a code problem, and it had probably been quietly getting worse for weeks.&lt;/p&gt;

&lt;p&gt;I opened the controller responsible for rendering the products page. On the surface, the code looked completely normal. Something like this.&lt;/p&gt;

&lt;p&gt;The controller fetched all active products, looped through them, and for each product pulled its images, its category name, and its average review rating to display on the storefront cards. Nothing about it looked dangerous. It read like clean, simple Laravel code, the kind you would write without thinking twice.&lt;/p&gt;

&lt;p&gt;But that loop was the trap.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Culprit Hiding in Plain Sight
&lt;/h2&gt;

&lt;p&gt;Here is the part that catches even experienced developers off guard. Eloquent, the ORM that powers database interactions in Laravel, is beautifully expressive. You can write &lt;code&gt;$product-&amp;gt;images&lt;/code&gt;, &lt;code&gt;$product-&amp;gt;category&lt;/code&gt;, &lt;code&gt;$product-&amp;gt;reviews&lt;/code&gt; and it just works, almost like magic. That magic is exactly what makes the N plus one problem so easy to miss.&lt;/p&gt;

&lt;p&gt;When you fetch a list of products with a single query, that part is efficient. The trouble starts the moment you loop through those products and access a relationship on each one individually, like calling &lt;code&gt;$product-&amp;gt;images&lt;/code&gt; inside a &lt;code&gt;foreach&lt;/code&gt; loop. Every single call like that fires its own separate database query.&lt;/p&gt;

&lt;p&gt;So if her store had 40 products showing on a page, and each product needed its images, its category, and its reviews loaded individually, that is not 40 extra queries. That is potentially 120 or more, on top of the original query just to fetch the products. As her catalog had grown over the months, from a modest 40 products to well over 500, that number had grown right alongside it. What might have been a mildly inefficient page at launch had quietly become a page firing north of 1,500 individual database queries every time someone tried to browse her store.&lt;/p&gt;

&lt;p&gt;Nobody had written bad code on purpose. The original implementation worked fine when the catalog was small. It is a mistake I have seen in projects far beyond hers, and one I actually cover in detail in my book, because it is one of the most common and most dangerous performance traps in modern web applications. It rarely announces itself with an error. It just gets slower, and slower, until one night it stops being usable altogether.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding the Exact Breaking Point
&lt;/h2&gt;

&lt;p&gt;Before touching anything, I wanted to be certain I understood the full scope of the problem, not just guess at it. I used Telescope's query view to trace exactly where the flood of queries was originating from, and confirmed it through Laravel Debugbar locally. Both pointed to the same three relationships being lazy loaded inside the products loop, images, category, and reviews.&lt;/p&gt;

&lt;p&gt;I asked her if anything had changed recently on the site, a new feature, a redesign, a plugin, anything at all. She said no, nothing recent, as far as she knew. That told me this was not a fresh bug someone had just introduced. This had been building gradually as her product catalog grew. Growth, in this case, was the thing that turned a hidden inefficiency into a full blown outage. That is often how these incidents happen. The code was never technically broken. It simply was not built to survive success.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fix
&lt;/h2&gt;

&lt;p&gt;The fix itself, once diagnosed, took far less time than the diagnosis did. This is almost always true with N plus one problems. The hard part is finding them, not fixing them.&lt;/p&gt;

&lt;p&gt;Instead of letting each product lazily pull its own relationships one at a time, I rewrote the query to eager load everything up front using Eloquent's &lt;code&gt;with()&lt;/code&gt; method. Rather than fetching products and then separately reaching back into the database for images, category, and reviews for every single row, the application now fetched products along with their images, category, and reviews in a small, fixed number of queries, regardless of whether the catalog had 40 products or 4,000.&lt;/p&gt;

&lt;p&gt;I also added a review count and average rating as calculated fields directly in the same eager load, rather than looping and calculating them in PHP afterward, which shaved off even more overhead. To protect against this ever silently creeping back in, I added a safeguard in the local development environment that throws a warning whenever a lazy relationship is accessed inside a loop, so any future code like this gets caught before it ever reaches production.&lt;/p&gt;

&lt;p&gt;The Moment It Clicked&lt;/p&gt;

&lt;p&gt;I refreshed the products page.&lt;/p&gt;

&lt;p&gt;It loaded instantly.&lt;/p&gt;

&lt;p&gt;I ran it again, timing it properly this time. Consistently under one second, down from nearly five minutes. Query count dropped from over 1,500 to just six.&lt;/p&gt;

&lt;p&gt;I called her back. It was almost 3 in the morning at this point, but she picked up on the first ring.&lt;/p&gt;

&lt;p&gt;"Try loading your products page now," I told her.&lt;/p&gt;

&lt;p&gt;There was a pause, then a short laugh of relief. "It is instant," she said. "Oh my goodness, it is actually instant."&lt;/p&gt;

&lt;p&gt;By the next morning, her order numbers had already started climbing back to normal. The store had not lost customers because people stopped wanting to buy from her. It had lost customers because, for a stretch of hours, her store had quietly made it nearly impossible for anyone to buy anything at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Incident Actually Taught Me
&lt;/h2&gt;

&lt;p&gt;A few things stuck with me long after that night.&lt;/p&gt;

&lt;p&gt;The first is that performance problems rarely arrive with a warning label. Nobody gets an alert that says a page is about to become five times slower than it was last month. It happens gradually, hidden behind code that looks perfectly reasonable, until growth or traffic finally exposes it all at once.&lt;/p&gt;

&lt;p&gt;The second is that monitoring is not optional once real money is involved. Her store did not have query level monitoring in place before that night. It does now. Tools like Laravel Telescope in staging, or a proper application performance monitoring service in production, would have flagged this problem weeks before it became an emergency phone call.&lt;/p&gt;

&lt;p&gt;The third, and maybe the most important, is that clean looking code is not the same as efficient code. The controller that caused all of this looked simple and readable. That is exactly why it slipped through. N plus one issues are sneaky precisely because they do not look like bugs. They look like normal, everyday Eloquent usage.&lt;/p&gt;

&lt;h3&gt;
  
  
  If You Are Running Laravel in Production, Check These Things
&lt;/h3&gt;

&lt;p&gt;If you maintain a Laravel application that touches real revenue, a few habits can save you from a night like this one.&lt;/p&gt;

&lt;p&gt;Turn on Laravel Telescope or Debugbar in a staging environment and actually watch the query count on your busiest pages, not just once, but as your data grows.&lt;/p&gt;

&lt;p&gt;Get comfortable with eager loading using with() any time you are looping through a collection and accessing a relationship inside that loop.&lt;/p&gt;

&lt;p&gt;Set a mental or literal query budget for key pages. If your products page or dashboard is firing more than a handful of queries, that is worth investigating before it becomes a crisis.&lt;/p&gt;

&lt;p&gt;Test your critical pages with realistic data volume, not just the 10 sample products you used during development. Problems like this one hide comfortably at small scale and only show their teeth once your catalog, your orders, or your users actually grow.&lt;/p&gt;

&lt;p&gt;And finally, have someone you can call in a genuine emergency, and make sure your systems are set up so that when that call comes, you can actually diagnose the problem quickly instead of guessing in the dark.&lt;/p&gt;

&lt;p&gt;Her store survived that night because the fix, once found, was straightforward. Not every story like this ends that cleanly, and this is far from the only crime scene I have walked into. Some cost companies their data. Some cost them their reputation. Most of them, like this one, hide quietly inside code that looks completely normal until the night it does not.&lt;/p&gt;

&lt;p&gt;That is exactly why I wrote &lt;a href="https://zuqolab.com/blog/code-crimes-the-book-every-developer-needs-before-their-next-code-review" rel="noopener noreferrer"&gt;Code Crimes, Security and Performance Mistakes in Modern Code&lt;/a&gt;. It walks through real mistakes like this one across Python, PHP, and JavaScript, the kind that quietly cost teams performance, money, and security, and it breaks down exactly how each one shows up, what it actually costs, and how to fix it the professional way, before it becomes your own 2 AM phone call.&lt;/p&gt;

&lt;p&gt;You can read more about the book &lt;a href="https://zuqolab.com/blog/code-crimes-the-book-every-developer-needs-before-their-next-code-review" rel="noopener noreferrer"&gt;here&lt;/a&gt;, or go straight to &lt;a href="https://www.amazon.com/dp/B0H678BFCK" rel="noopener noreferrer"&gt;Amazon &lt;/a&gt;to grab a copy.&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>phpperformance</category>
      <category>databaseoptimization</category>
      <category>nplus1query</category>
    </item>
    <item>
      <title>The Hidden Security and Performance Mistakes Killing Your Python, PHP, and JavaScript Projects</title>
      <dc:creator>Akeem O. Salau</dc:creator>
      <pubDate>Fri, 03 Jul 2026 10:22:06 +0000</pubDate>
      <link>https://dev.to/obrainwave/performance-mistakes-1162</link>
      <guid>https://dev.to/obrainwave/performance-mistakes-1162</guid>
      <description>&lt;p&gt;Every week, another company makes headlines for a data breach or a site that crawled to a halt under normal traffic. In most cases, the root cause was not some sophisticated attack or freak server failure. It was a mistake that had been sitting quietly in the codebase for months, waiting to be noticed by the wrong person or the wrong spike in users.&lt;/p&gt;

&lt;p&gt;Security and performance are often treated as separate concerns, handled by different teams at different stages of a project. In reality, they are deeply connected. A slow query is often an unindexed query, and an unindexed query is often the same query that lets an attacker enumerate your entire user table through a poorly designed endpoint. The same corners that get cut for speed of development tend to be the corners that create both problems at once.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Security and Performance Are Two Sides of the Same Coin
&lt;/h2&gt;

&lt;p&gt;When code is rushed, developers reach for the fastest working solution rather than the correct one. That might mean skipping input validation because "the frontend already checks it," or writing a database query inside a loop because refactoring it properly would take longer. These shortcuts compile, they pass the demo, and they ship. Then six months later, traffic grows, the loop query brings the server down, or a curious user changes a parameter in the URL and gets access to data they should never see.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Python Mistakes Developers Keep Repeating
&lt;/h3&gt;

&lt;p&gt;Python's readability gives developers a false sense of safety. Some of the most common issues include using eval() or pickle on data that comes from users, which can allow arbitrary code execution. Another frequent problem is the N+1 query pattern in Django and Flask apps, where a single page load triggers hundreds of unnecessary database calls because relationships were not eager loaded. Developers also tend to skip proper exception handling in production code, exposing stack traces that hand attackers a roadmap of the internal system.&lt;/p&gt;

&lt;h3&gt;
  
  
  PHP's Silent Killers
&lt;/h3&gt;

&lt;p&gt;PHP still powers a huge share of the web, and many of its long standing habits carry real risk. SQL injection remains alive and well in codebases that concatenate user input directly into queries instead of using prepared statements. File upload handlers are another weak point, often accepting files without properly checking their type or where they get stored, which opens the door to remote code execution. On the performance side, many PHP applications still run without any caching layer for repeated database reads, meaning every page load hits the database from scratch even when the data has not changed in hours.&lt;/p&gt;

&lt;h3&gt;
  
  
  JavaScript's Speed and Security Traps
&lt;/h3&gt;

&lt;p&gt;JavaScript sits in a strange spot because it runs on both the client and the server, and mistakes on either side create very different problems. On the frontend, developers often trust data from local storage or cookies without realizing it can be manipulated by anyone with browser dev tools open. On the backend, Node.js applications frequently suffer from blocking the event loop with heavy synchronous operations, which quietly tanks performance for every user connected at that moment. Dependency sprawl is another issue unique to the JavaScript ecosystem, where a single npm install can pull in hundreds of packages, some of which carry known vulnerabilities that never get patched because nobody is tracking them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Cost of Ignoring These Mistakes
&lt;/h2&gt;

&lt;p&gt;These are not abstract concerns. A breach means legal exposure, lost customer trust, and in many regions, regulatory penalties. A slow application means abandoned carts, lower search rankings, and users who quietly switch to a competitor without ever filing a complaint. The frustrating part is that most of these problems are preventable with patterns that take barely more effort than the shortcut that caused them.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Fix This Before It Costs You
&lt;/h2&gt;

&lt;p&gt;The fix is rarely a rewrite. It is usually a shift in habits: validating input at every layer, not just the frontend, using parameterized queries by default, profiling database calls before they become a bottleneck, and treating dependency updates as a routine task rather than something to deal with after an incident. These are the exact patterns broken down in detail in my book, &lt;a href="https://zuqolab.com/blog/code-crimes-the-book-every-developer-needs-before-their-next-code-review" rel="noopener noreferrer"&gt;Code Crimes: Security &amp;amp; Performance Mistakes in Modern Code&lt;/a&gt;, which walks through real, recurring mistakes in Python, PHP, and JavaScript and shows exactly how to catch them before they reach production.&lt;/p&gt;

&lt;p&gt;If you write code that other people depend on, whether that is a small business tool or a platform with thousands of daily users, these are not optional lessons. They are the difference between a codebase that quietly holds up under pressure and one that quietly falls apart.&lt;/p&gt;

&lt;h3&gt;
  
  
  Get the book:
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://amazon.com/dp/B0H678BFCK" rel="noopener noreferrer"&gt;amazon.com/dp/B0H678BFCK&lt;/a&gt;&lt;br&gt;
&lt;a href="https://amazon.co.uk/dp/B0H678BFCK" rel="noopener noreferrer"&gt;amazon.co.uk/dp/B0H678BFCK&lt;/a&gt;&lt;br&gt;
&lt;a href="https://amazon.fr/dp/B0H678BFCK" rel="noopener noreferrer"&gt;amazon.fr/dp/B0H678BFCK&lt;/a&gt;&lt;/p&gt;

</description>
      <category>softwaresecurity</category>
      <category>codeperformance</category>
      <category>codingbestpractices</category>
      <category>securedcode</category>
    </item>
    <item>
      <title>This One Regex Line Can Take Your Python App Offline (And You'd Never Suspect It)</title>
      <dc:creator>Akeem O. Salau</dc:creator>
      <pubDate>Sat, 20 Jun 2026 21:07:03 +0000</pubDate>
      <link>https://dev.to/obrainwave/this-one-regex-line-can-take-your-python-app-offline-and-youd-never-suspect-it-1cnb</link>
      <guid>https://dev.to/obrainwave/this-one-regex-line-can-take-your-python-app-offline-and-youd-never-suspect-it-1cnb</guid>
      <description>&lt;p&gt;You write a regex pattern to validate a token. Something simple. You test it with real input, it works perfectly, you ship it, and you move on to the next ticket. Weeks later your app grinds to a halt under what looks like a tiny amount of traffic. No memory leak. No database lock. No obvious culprit in your logs. Just one thread, pinned at 100% CPU, doing absolutely nothing useful.&lt;/p&gt;

&lt;p&gt;The cause was sitting in your code the whole time. It was that regex.&lt;/p&gt;

&lt;p&gt;This is one of the sneakier bugs a backend developer can write, because it doesn't fail the way bugs normally fail. It doesn't throw an exception. It doesn't return the wrong value. It just runs forever on a tiny, perfectly ordinary-looking string. And if you're validating anything user-submitted, alphanumeric tokens, CSV fields, tracking IDs, usernames, you have probably written a version of this exact mistake without knowing it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup: a regex that looks completely reasonable
&lt;/h2&gt;

&lt;p&gt;Say you're validating tokens submitted by users. Something like a tracking ID or an alphanumeric code. You want to allow letters, numbers, and underscores, and you want the whole string to match from start to end. A natural first attempt looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;

&lt;span class="c1"&gt;# Looks safe, but the nested '+' and '*' create exponential paths
&lt;/span&gt;
&lt;span class="n"&gt;VULNERABLE_REGEX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;^([a-zA-Z0-9_]+)*$&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;validate_token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;VULNERABLE_REGEX&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_input&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you test this with valid tokens, it works instantly. Feed it user_123_abc and you get a clean True in microseconds. Everything looks fine. It passes code review. It passes your test suite. It ships.&lt;/p&gt;

&lt;p&gt;The problem only shows up when someone sends a string that is almost valid but not quite, something like thirty or forty valid characters followed by a single invalid one at the very end. At that point, the regex engine doesn't just fail quickly. It tries to fail in every possible way before giving up, and the number of ways grows exponentially with the length of the input.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this happens: nested quantifiers are ambiguous
&lt;/h2&gt;

&lt;p&gt;The root issue is the structure ([a-zA-Z0-9_]+)&lt;em&gt;. You have a repetition group (+) nested inside another repetition group (&lt;/em&gt;). To the engine, this creates massive ambiguity about how to split the string into pieces. A run of twenty valid characters could be read as one big group of twenty, or two groups of ten, or four groups of five, or twenty groups of one, and so on. There are an enormous number of equally valid ways to partition that run.&lt;/p&gt;

&lt;p&gt;As long as the entire string matches, the engine doesn't care which partition it used, so it picks one and moves on instantly. But the moment something downstream fails to match, like that final invalid character, the engine has to go back and try every other partition of the string before it can conclude that none of them work either. That backtracking process is where the exponential blow-up happens.&lt;/p&gt;

&lt;p&gt;This is called Regular Expression Denial of Service, or ReDoS, and it's one of the few bug classes where a single request from a single user can take down an entire server thread with nothing but a moderately sized string.&lt;/p&gt;

&lt;h2&gt;
  
  
  The professional impact: this is a denial-of-service vector, not a quirky edge case
&lt;/h2&gt;

&lt;p&gt;It's tempting to file this away as a theoretical concern, but the impact is very real and very practical:&lt;/p&gt;

&lt;p&gt;A malicious actor doesn't need a sophisticated exploit. They just need to find one input field in your application backed by a vulnerable regex, and send a crafted string of thirty to fifty characters. That's it. The regex engine will burn 100% of a CPU core trying to resolve the match, and because most web frameworks process requests on a limited pool of threads or within a single event loop, that one request can block everything else behind it.&lt;/p&gt;

&lt;p&gt;Send a handful of these requests in parallel, and you can exhaust your entire application's available threads or event-loop cycles. The service becomes completely unresponsive to legitimate users, not because your database is slow, not because you're under heavy genuine traffic, but because of one badly shaped regular expression doing busywork that produces no useful output at all.&lt;/p&gt;

&lt;p&gt;In a cloud environment, this gets worse before it gets better. Your monitoring sees CPU usage spike and, doing exactly what it's designed to do, triggers auto-scaling. You spin up more instances to handle the "load." The attacker's strings hit those instances too. You're now paying for compute capacity to process malicious garbage, and your bill goes up at the same rate your service goes down.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix: stop the engine from backtracking
&lt;/h2&gt;

&lt;p&gt;There are two complementary strategies here, and the strongest approach uses both.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option A:&lt;/strong&gt; Linearize the pattern. In a lot of cases, the nested quantifier isn't even doing anything useful. If you actually walk through what ([a-zA-Z0-9_]+)* is trying to express, you'll often find it's logically identical to a much simpler pattern with no nesting at all:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;

&lt;span class="c1"&gt;# Linearize: remove the nested duplication entirely
&lt;/span&gt;
&lt;span class="n"&gt;CLEAN_REGEX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;^[a-zA-Z0-9_]*$&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This matches exactly the same set of strings as the vulnerable version, but it does so in linear time. There's no ambiguity for the engine to resolve, so there's nothing to backtrack into.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option B:&lt;/strong&gt; Use possessive quantifiers (Python 3.11+). If your pattern genuinely needs the grouping structure for some other reason, modern Python gives you a tool that removes the ambiguity directly. A possessive quantifier, written with ++, tells the engine that once a group has matched, it should never backtrack into that match later. If the overall pattern eventually fails, the engine fails immediately instead of trying every other way to split the string.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;

&lt;span class="c1"&gt;# Possessive quantifier: once matched, the group is locked and won't backtrack
&lt;/span&gt;
&lt;span class="n"&gt;MODERN_SAFE_REGEX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;^([a-zA-Z0-9_]+)++$&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;validate_token_safe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MODERN_SAFE_REGEX&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_input&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This converts the pattern's worst-case behavior from exponential to predictable and linear, while keeping the original group structure intact if you need it for something like capturing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Defense in depth: length limits and timeouts
&lt;/h2&gt;

&lt;p&gt;Fixing the regex itself is the most important step, but it shouldn't be the only line of defense. Two cheap additions make the difference between "fixed" and "actually hardened":&lt;/p&gt;

&lt;p&gt;Cap the input length before you ever touch the regex engine.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;MAX_TOKEN_LENGTH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;128&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;validate_token_safe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;MAX_TOKEN_LENGTH&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MODERN_SAFE_REGEX&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_input&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even a vulnerable regex becomes far less dangerous when it's only ever fed strings under, say, 128 characters. Exponential growth is exponential, but it still takes a meaningful string length before the slowdown becomes severe. A hard cap upstream buys you a real safety margin even if a vulnerable pattern slips through review somewhere else.&lt;/p&gt;

&lt;p&gt;For systems where the stakes are higher, use a regex engine with an explicit timeout. Python's built-in re module doesn't support timeouts natively, but the third-party regex package does:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;regex&lt;/span&gt;

&lt;span class="n"&gt;SAFE_REGEX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;^[a-zA-Z0-9_]*$&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.05&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With a timeout in place, even a pathological input that somehow gets past your other defenses can only burn CPU for a bounded amount of time before the match attempt is aborted. This turns a potential full outage into, at worst, a single failed request.&lt;/p&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;Every regular expression that processes input coming from outside your system, a user, an API client, an uploaded file, is a potential denial-of-service vector, and most of the time it looks completely harmless until someone goes looking for the right input to break it.&lt;/p&gt;

&lt;h2&gt;
  
  
  🔴 Going Live: 5 Real Exploits, Straight From Code Crimes
&lt;/h2&gt;

&lt;p&gt;SQL injection. Broken access control. A password reset bypass that takes one single character. And more, walked through live, straight out of my upcoming book, Code Crimes.&lt;/p&gt;

&lt;p&gt;📅 July 18, 2026 · 7:00 PM (WAT)&lt;br&gt;
📍 Facebook Live · Free to attend&lt;br&gt;
👉 &lt;a href="https://facebook.com/share/1DtgRk8WLH" rel="noopener noreferrer"&gt;https://facebook.com/share/1DtgRk8WLH&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  A few habits make this risk manageable:
&lt;/h3&gt;

&lt;p&gt;Avoid nesting one repetition group inside another unless you've specifically checked whether the nesting is even necessary. Often it isn't, and a flatter pattern does the same job in linear time.&lt;/p&gt;

&lt;p&gt;Set a hard length limit on any input before it reaches a regex, regardless of how confident you are in the pattern itself. It costs you one line of code and removes an entire category of worst-case behavior.&lt;/p&gt;

&lt;p&gt;If you're on Python 3.11 or later and genuinely need grouping, reach for possessive quantifiers or atomic groups rather than ordinary backtracking groups.&lt;/p&gt;

&lt;p&gt;For anything security-sensitive, consider an engine that supports a hard timeout, so a single bad match attempt can never become a single bad outage.&lt;/p&gt;

&lt;p&gt;And test for this deliberately. Don't just test your regex against valid input, that will always look fast. Test it against long, almost-valid strings that fail right at the very end. If your matching time grows faster than linearly as you lengthen that input, you've found a ReDoS bug before an attacker did.&lt;/p&gt;

</description>
      <category>regexdenialofservice</category>
      <category>catastrophicbacktracking</category>
      <category>inputvalidationpython</category>
      <category>denialofservicevulnerability</category>
    </item>
    <item>
      <title>Stop Fighting Your AI Coding Agent: A Developer's Guide to Thinking in Collaboration, Not Commands</title>
      <dc:creator>Akeem O. Salau</dc:creator>
      <pubDate>Sun, 31 May 2026 08:30:06 +0000</pubDate>
      <link>https://dev.to/obrainwave/stop-fighting-your-ai-coding-agent-a-developers-guide-to-thinking-in-collaboration-not-commands-nmi</link>
      <guid>https://dev.to/obrainwave/stop-fighting-your-ai-coding-agent-a-developers-guide-to-thinking-in-collaboration-not-commands-nmi</guid>
      <description>&lt;p&gt;Most developers treat AI coding agents like a search engine that writes code. That is the root of every frustration. This guide reframes how you think about AI pair programming so you spend less time wrestling and more time shipping.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Problem Is Not the AI
&lt;/h2&gt;

&lt;p&gt;If you have ever watched an AI agent confidently refactor your entire codebase in the wrong direction, you know the particular dread that follows. You paste in a long prompt, wait, and then receive something that technically compiles but bears no resemblance to what you meant. So you re-prompt, re-explain, and re-iterate until frustration tips into rage.&lt;/p&gt;

&lt;p&gt;Here is what nobody tells you upfront: the AI did not fail. Your mental model of what the AI is doing was just incomplete. The frustration is almost never a capability gap. It is a collaboration gap.&lt;/p&gt;

&lt;p&gt;Reframe: An AI coding agent is not an autocomplete engine with ambitions. It is a highly capable but context-blind collaborator who has read every programming book ever written but has never once seen your project, your team conventions, or what you meant when you said "clean this up."&lt;/p&gt;

&lt;h2&gt;
  
  
  Understand What the Agent Actually Sees
&lt;/h2&gt;

&lt;p&gt;Every frustrating interaction with an AI agent traces back to one core mismatch: you are thinking in full project context, and the agent is thinking in token windows. It cannot smell the legacy code debt three files over. It does not know that "the old auth system" refers to a module you are actively deprecating. It only knows what you gave it, plus everything it learned from training.&lt;/p&gt;

&lt;p&gt;This is not a flaw you work around. It is the constraint you design around. Once you accept that the agent needs context served to it explicitly rather than assumed, the whole dynamic shifts from you being disappointed by it to you being a skilled director who knows how to brief your most capable contractor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give It a Role Before You Give It a Task
&lt;/h2&gt;

&lt;p&gt;One of the highest-leverage habits in agentic coding is front-loading your prompt with a clear role definition before any instruction. Not just "you are a senior engineer" but something more specific: "You are refactoring a Node.js API that uses Express and Prisma, and our convention is to never mutate request objects. Keep changes minimal and backward-compatible."&lt;/p&gt;

&lt;p&gt;Role-setting collapses half the back-and-forth before it ever starts. The agent now has a lens through which to evaluate every decision it makes. Without a role, it optimizes for generic correctness. With a role, it optimizes for your correctness.&lt;/p&gt;

&lt;p&gt;A common mistake: asking the agent to both understand AND implement in one shot on a complex task. Break those into two sequential prompts. Ask it to explain its approach first. You will catch misunderstandings at the cheapest possible moment, before any code is written.&lt;/p&gt;

&lt;h2&gt;
  
  
  Treat Long Sessions Like a Conversation With a Forgetful Genius
&lt;/h2&gt;

&lt;p&gt;Agentic sessions decay. The longer a coding thread goes, the more likely the agent is operating on diluted context. It starts making decisions based on its most recent exchanges rather than your earliest setup instructions. This is not a bug. This is physics. Context windows have limits.&lt;/p&gt;

&lt;p&gt;The practical fix is periodic resetting. After completing any meaningful milestone in a session, summarize the current state yourself and paste it into a fresh session. Yes, this feels manual. But it eliminates the ghost instructions that silently corrupt long runs. Think of it as committing your working memory to disk before the power goes out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scope Is the Single Most Underrated Prompt Skill
&lt;/h2&gt;

&lt;p&gt;The developers who work most fluidly with AI agents share one trait: they are ruthless about scope. Every prompt they write targets one specific, bounded thing. Not "refactor my auth module" but "in auth/middleware.ts, replace the manual token expiry check on line 48 with a call to the existing validateTokenExpiry utility and handle the two error cases it can throw."&lt;/p&gt;

&lt;p&gt;Tight scope means tight output. Tight output means fast review. Fast review means fewer spirals. The agent can handle big tasks. Your frustration tolerance usually cannot. Slice the problem until you can evaluate the agent's output in under two minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use AI Agents for Thinking, Not Just Typing
&lt;/h2&gt;

&lt;p&gt;Most developers invoke their agent only when they want code produced. This leaves enormous value on the table. The most frustration-free developers use the agent as a thinking partner long before they need output. They paste in a failing test and ask the agent what is likely wrong. They describe a system they are about to build and ask the agent to poke holes in the design. They share a vague requirement and ask it to surface the ambiguities before any implementation begins.&lt;/p&gt;

&lt;p&gt;When you use the agent for thinking, you arrive at the code-generation phase with far fewer surprises. The agent then functions in its strongest mode: executing a well-defined plan rather than improvising a poorly-defined one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Review Agent Output Like a Senior Engineer, Not a Rubber Stamp
&lt;/h2&gt;

&lt;p&gt;Here is where a lot of developers quietly set themselves up for frustration: they accept agent output without real scrutiny, ship it, and then spend three hours debugging something the agent introduced. The agent is not to blame. Accepting code without review is always on the developer.&lt;/p&gt;

&lt;p&gt;Build a fast review habit. Check that the agent touched only what you asked it to touch. Scan for new dependencies it introduced without telling you. Run your tests before moving on. The review step is not a tax on using AI. It is the step that makes everything else trustworthy.&lt;/p&gt;

&lt;p&gt;A strong mental model: think of the agent as a very fast junior developer who produces solid first drafts. You would never ship a junior's draft unread. Same rule applies here, regardless of how confident the output looks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Manage Your Emotional State as Part of the Workflow
&lt;/h2&gt;

&lt;p&gt;Frustration with AI agents compounds when it goes unmanaged. You send a worse prompt when you are irritated. The agent produces a worse result. You are more irritated. The spiral is real, and the entry point is almost always a prompt written in haste or emotion.&lt;/p&gt;

&lt;p&gt;The professional move is to treat a bad agent output the way you would treat a failed test. Something in the setup was wrong. Diagnose, adjust, and retry with a clearer head. Take the prompt you wrote in frustration and rewrite it with the precision of a bug report. Be specific about what went wrong, what you expected, and what you need different.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build a Prompt Library You Actually Use
&lt;/h2&gt;

&lt;p&gt;The developers who are most calm around agentic tools have a personal collection of prompt patterns that work for them. Not a generic list downloaded from a blog post, but actual prompts they have refined through real use: their preferred way to kick off a debugging session, their standard context block for their main codebase, their template for asking the agent to review rather than rewrite.&lt;/p&gt;

&lt;p&gt;Over time, this library becomes a system. You stop drafting every prompt from scratch and start assembling from known-good pieces. The cognitive load drops, the outputs improve, and the frustration disappears almost entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Agentic Coding Is a Skill, Not a Shortcut
&lt;/h2&gt;

&lt;p&gt;The developers who feel most at home with AI coding agents are not the ones who found a magic prompt or a better model. They are the ones who invested time in understanding how to communicate effectively with a system that is genuinely different from every tool they used before.&lt;/p&gt;

&lt;p&gt;Treat it like learning a new language. The early stages are awkward and occasionally embarrassing. The middle stages involve a lot of translation overhead. But once the communication clicks, you are building at a speed that would have seemed unrealistic two years ago.&lt;/p&gt;

&lt;p&gt;The frustration was never a sign you should stop. It was always a signal about where the collaboration needed to improve. Now you know exactly where to look.&lt;/p&gt;

</description>
      <category>aiagenticcoding</category>
      <category>aipairprogramming</category>
      <category>developerproductivity</category>
      <category>aifrustrationtips</category>
    </item>
  </channel>
</rss>
