Skip to content

$ cat security-tip-the-trap-that-caught-me.md

Security Tip: The Trap That Caught Me!

A Laravel security tip about a client's LivewireBotBanner class, which bans IPs that trigger Livewire exceptions such as requests for nonexistent components or updates to locked properties. The author covers how it was found, its IP-based limits, false positive risks, and why logging and alerting should accompany it.

Securing Laravel05 Sept 2026 · 4 min read · summary
Read on securinglaravel.com ↗
One of the cool parts of doing Laravel Security Audits and Penetration Tests is seeing how my clients go about solving different problems in their code, and some of their creative security implementations. One such example is a class called LivewireBotBanner, which I came across during a recent audit. I discovered it by accident when I was attempting to override some Livewire properties that were incorrectly configured. I made a request to a locked property and suddenly hit 403 errors on all requests. I assumed it was a WAF being overly twitchy, rotated my IP address, and got back in. But it didn't feel right, my request shouldn't have looked suspicious - especially not compared to some of the other requests I'd made. So I went digging... I quickly discovered a global middleware called EnforceBotProtection which looks for suspicious User-Agents (i.e. curl, wget, python, etc), and that made me smile, but clearly wasn't the cause of my 403 - Burp's browser (which I use during pentests) isn't that lazy. I kept digging and saw this line in the middleware: if (LivewireBotBanner::isBanned($ip)) { abort(403, 'Forbidden'); } After digging into LivewireBotBanner, I realised it was a class dedicated to identifying and blocking anyone making suspicious requests to Livewire. The global exception handler (\App\Exceptions\Handler) was configured to catch two notable Livewire exceptions, and call LivewireBotBanner::ban() to do the actual banning: if ( $e instanceof ComponentNotFoundException || $e instanceof CannotUpdateLockedPropertyException ) { LivewireBotBanner::ban($request, $e); } Such a simple and elegant trick: ComponentNotFoundException is fired when a component that doesn't exist is requested, and CannotUpdateLockedPropertyException is fired when the user attempts to change a locked property. Neither of these should happen during normal operation, so they provide strong indications that the user is doing something suspicious. The automatic response to these exceptions was to block the user by IP address, returning a 403 Forbidden, which feels like a suitable response when you're confident it will only be hit by someone doing something intentionally suspicious. That said, there are a couple of caveats I should mention: This system blocks by IP address, and it's trivial to rotate your IP if you know what you're doing. In other words, it won't stop a determined attacker. However, you could enhance this protection by introducing a temporary ban on the user account itself - lock them out for 15 minutes, or send an email with an unlock link. The latter serves a dual purpose of notifying the user of suspicious activity, in case of account takeover. (In defence of my client, they have public Livewire forms where IP is the only unique identifier available.) False positives are possible. A buggy deploy or misconfigured properties could trigger these exceptions, locking out the user. So ensure you've got suitable tests and QA, and an unlock method. Couple this with logging and alerting, so your team are notified if users start getting blocked. This serves as both a warning system for an active attack, and an alert if something breaks and legitimate requests are blocked. My recommendation is to explore something like this in your code. When you've got exceptions and errors that should only occur during suspicious activity, having some alerting or active defences in place surrounding these won't cost you much, but may slow down or prevent an attacker from poking at your app further. 🤓 Full credit for this idea goes to Janos Burkhalter at ValueQuest, with permission given for me to write about it. Found this security tip useful? 👍 Subscribe now to get weekly Security Tips straight to your inbox - practical, actionable advice to help you build safer apps. Want to go deeper? 🤓 Upgrade to a Premium Subscription for exclusive monthly In Depth articles. Your support directly funds my security work in the Laravel community. 🥰 Need a second set of eyes on your code? Book a Laravel Security Audit and Penetration Test - or, for something more focused, a lighter-weight Security Review. Finally, connect with me on Twitter, Bluesky, phpc.social, and LinkedIn.

source

This is a summary of an article first published by Securing Laravel. The full piece is on their site.

Open original