From 3a8775fa52759d9269629d1ef0235feb5784df61 Mon Sep 17 00:00:00 2001 From: HolgerHatGarKeineNode <123783602+HolgerHatGarKeineNode@users.noreply.github.com> Date: Thu, 4 Jun 2026 11:45:02 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=9B=A1=EF=B8=8F=20**Add=20robust=20Livewi?= =?UTF-8?q?re=20payload=20validation=20and=20throttling**=20-=20=E2=9C=85?= =?UTF-8?q?=20Implemented=20handling=20for=20`CorruptComponentPayloadExcep?= =?UTF-8?q?tion`=20to=20prevent=20logging=20noise=20and=20improve=20except?= =?UTF-8?q?ion=20management.=20-=20=F0=9F=9B=A0=EF=B8=8F=20Added=20IP-base?= =?UTF-8?q?d=20throttling=20(120=20requests/min)=20for=20the=20`/livewire/?= =?UTF-8?q?update`=20endpoint=20with=20middleware=20integration=20for=20be?= =?UTF-8?q?tter=20traffic=20control.=20-=20=E2=9C=85=20Introduced=20unit?= =?UTF-8?q?=20tests=20to=20validate=20throttle=20settings=20and=20middlewa?= =?UTF-8?q?re=20application.=20-=20=F0=9F=A7=AA=20Enhanced=20tests=20for?= =?UTF-8?q?=20ensuring=20silent=20handling=20of=20corrupt=20payload=20scen?= =?UTF-8?q?arios=20and=20reduced=20log=20noise.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Providers/AppServiceProvider.php | 12 ++++++++- bootstrap/app.php | 12 +++++++++ tests/Feature/LivewireExploitProbeTest.php | 15 +++++++++++ tests/Feature/LivewireUpdateThrottleTest.php | 27 ++++++++++++++++++++ 4 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 tests/Feature/LivewireUpdateThrottleTest.php diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 0289854..1b1a7f4 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -43,7 +43,7 @@ class AppServiceProvider extends ServiceProvider Livewire::setUpdateRoute(function ($handle) { return Route::post('/livewire/update', $handle) - ->middleware(['web', Sample::rate(0)]); + ->middleware(['web', 'throttle:livewire', Sample::rate(0)]); }); Nightwatch::user(fn (Authenticatable $user) => [ @@ -65,5 +65,15 @@ class AppServiceProvider extends ServiceProvider RateLimiter::for('calendar', function (Request $request) { return Limit::perMinute(60)->by($request->ip()); }); + + // Generous backstop for the shared `/livewire/update` endpoint. A single + // active user stays far below this: the only sustained generator is the + // login page's `wire:poll.4s` at ~15 req/min, plus interaction bursts. + // 120/min leaves headroom for several users behind one NAT while still + // capping abusive replay/scan traffic. Keyed by the real client IP + // (trustProxies('*') resolves X-Forwarded-For). + RateLimiter::for('livewire', function (Request $request) { + return Limit::perMinute(120)->by($request->ip()); + }); } } diff --git a/bootstrap/app.php b/bootstrap/app.php index 766217f..6dcf364 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -10,6 +10,7 @@ use Illuminate\Http\Request; use Livewire\Exceptions\MethodNotFoundException; use Livewire\Features\SupportFileUploads\MissingFileUploadsTraitException; use Livewire\Features\SupportLifecycleHooks\DirectlyCallingLifecycleHooksNotAllowedException; +use Livewire\Mechanisms\HandleComponents\CorruptComponentPayloadException; use Stefro\LaravelLangCountry\Middleware\LangCountrySession; return Application::configure(basePath: dirname(__DIR__)) @@ -100,6 +101,17 @@ return Application::configure(basePath: dirname(__DIR__)) if ($isLivewireExploitProbe($e)) { return false; } + + // Bots replay `/livewire/update` with a mutated snapshot whose HMAC + // checksum no longer matches its [name, id, data]. Checksum::verify() + // rejects these, so the rejection is the tamper signature, not an app + // fault — we silence the report noise. Rendering is left untouched: + // the exception already returns a native 419 on its own. + if ($e instanceof CorruptComponentPayloadException) { + return false; + } + + return null; }); $exceptions->render(function (Throwable $e, Request $request) use ($isStaleLivewireAsset, $isStaleCompiledView, $isMissingFileUploadsTrait, $isLivewireExploitProbe) { diff --git a/tests/Feature/LivewireExploitProbeTest.php b/tests/Feature/LivewireExploitProbeTest.php index d0462b1..10ae6a1 100644 --- a/tests/Feature/LivewireExploitProbeTest.php +++ b/tests/Feature/LivewireExploitProbeTest.php @@ -4,6 +4,7 @@ use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Route; use Livewire\Exceptions\MethodNotFoundException; use Livewire\Features\SupportLifecycleHooks\DirectlyCallingLifecycleHooksNotAllowedException; +use Livewire\Mechanisms\HandleComponents\CorruptComponentPayloadException; it('returns 400 for lifecycle-hook probing instead of 500', function () { Route::get('/_test/livewire-lifecycle-probe', function () { @@ -42,3 +43,17 @@ it('still surfaces genuine method-not-found bugs', function () { expect($this->get('/_test/livewire-real-method-not-found')->status())->not->toBe(400); }); + +it('does not report corrupt Livewire snapshot payloads', function () { + Log::spy(); + + Route::get('/_test/livewire-corrupt-payload', function () { + throw new CorruptComponentPayloadException; + }); + + $this->get('/_test/livewire-corrupt-payload'); + + Log::shouldNotHaveReceived('error'); + Log::shouldNotHaveReceived('critical'); + Log::shouldNotHaveReceived('emergency'); +}); diff --git a/tests/Feature/LivewireUpdateThrottleTest.php b/tests/Feature/LivewireUpdateThrottleTest.php new file mode 100644 index 0000000..a4b9e32 --- /dev/null +++ b/tests/Feature/LivewireUpdateThrottleTest.php @@ -0,0 +1,27 @@ +not->toBeNull(); + + $request = Request::create('/livewire/update', 'POST'); + $request->server->set('REMOTE_ADDR', '203.0.113.10'); + + $limit = $limiter($request); + + expect($limit->maxAttempts)->toBe(120) + ->and($limit->decaySeconds)->toBe(60) + ->and($limit->key)->toBe('203.0.113.10'); +}); + +it('applies the livewire throttle middleware to the update route', function () { + $route = Route::getRoutes()->getByName('livewire.update'); + + expect($route)->not->toBeNull() + ->and($route->gatherMiddleware())->toContain('throttle:livewire'); +});