Search by

edulazaro / wirefence

edulazaro

Fences Livewire apps against tampered requests: a property sent a value no form can produce, a locked property, an unknown property or method. Answers 400 instead of a 500 in the log, and blocks the address for a while.

Package info

github.com/edulazaro/wirefence

pkg:composer/edulazaro/wirefence

Statistics

Installs: 13

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

1.0.0 2026-10-23 02:41 UTC

This package is auto-updated.

Last update: 2026-10-23 02:44:11 UTC


README

Wirefence

wirefence

Tests Latest Stable Version Total Downloads PHP Version License

A fence for Livewire applications. Bots probe every Livewire site they find by posting forged updates to its update endpoint: an array where a form sends text, a locked property, a property or method the component does not have. Each one used to reach your log as a 500, burying your real errors under pages of Cannot assign array to property ... of type string. wirefence answers them with a 400, writes one warning line and blocks the address from the whole application for a day.

Laravel 12 and 13 · Livewire 3.6.4 and up, and 4 · Volt included · no JavaScript · one table.

Install

The package is auto-discovered, so installing it and migrating its table is all it takes.

composer require edulazaro/wirefence
php artisan migrate

It requires Livewire 3.6.4 or later on purpose: earlier versions carry CVE-2025-54068, the remote code execution these bots were looking for in the first place.

What gives a request away

Each check looks at the request before Livewire assigns or calls anything, through Livewire's own hooks, so it behaves the same on Livewire 3 and 4. None of them can be tripped from a page:

Signal What arrived Blocks after
type_mismatch an array or object for a property typed string, int, float or bool (nullable or in a union of those) 1
locked_property an update to a #[Locked] property 1
unknown_property an update to a property the component does not have, or that is not public 3
unknown_method a call to a method the component does not have, or that is not public 3

The last two take three strikes within ten minutes because a tab left open across a deploy that renamed a property or method can send the old name once or twice. Livewire's internal calls ($refresh, $set, __dispatch, _startUpload...) are never counted.

Text typed into a number field is a person's mistake, not a probe, and is left to Livewire. So is any TypeError your own component code raises.

What happens then

Every tampered request is answered with a 400 and one warning line in the log, with the address, the signal, the component and the property or method.

Once an address reaches a signal's threshold it is blocked for 24 hours: every request it makes to the application, Livewire or not, gets a 403 from the first middleware, before sessions, the database or Livewire. A ClientBlocked event is dispatched, for a mail or a Slack message:

Event::listen(\EduLazaro\Wirefence\Events\ClientBlocked::class, function ($event) {
    // $event->ip, ->signal, ->component, ->target, ->until, ->strikes
});

Addresses it never blocks

Blocking the wrong address would lock out real users, so some are never blocked, only answered with the 400.

  • Private and reserved addresses. Seeing one means the application does not see its visitors' real address, and blocking it would block everybody behind it. A warning in the log says so.
  • The allow list, addresses or CIDR ranges.
  • Cloudflare's own ranges, when the Cloudflare option is on.

wirefence trusts the address Laravel gives it, so configure trusted proxies if a proxy or load balancer sits in front. Behind Cloudflare without that, turn on WIREFENCE_CLOUDFLARE=true: a request coming from one of Cloudflare's ranges is then judged by its CF-Connecting-IP header, which nobody else can use to impersonate a visitor.

Commands

Three commands cover what an administrator needs.

php artisan wirefence:list            # blocked right now (--all for the history)
php artisan wirefence:unblock 203.0.113.7
php artisan wirefence:prune           # delete old records now

Storage

The cache answers "is this address blocked" on every request, so a visitor who is not costs one cache lookup and nothing else. The wirefence_blocks table is the record behind it: one row per address, with what gave it away the last time, how many times it has been blocked and until when. If the cache is cleared, a blocked bot gets one more try, and that try blocks it again.

An address is personal data, so rows whose block ended more than 30 days ago are deleted every day. That runs through Laravel's scheduler, so it needs schedule:run in your crontab; without it, run wirefence:prune by hand. Blocking works either way.

Configuration

Every threshold and duration can be changed by publishing the config file.

php artisan vendor:publish --tag=wirefence-config
Key Default
enabled true (WIREFENCE_ENABLED) off, Livewire behaves as without the package
signals.* 1, 1, 3, 3 strikes before blocking; 0 turns a signal off
window_minutes 10 the window strikes are counted in
block_minutes 1440 how long a block lasts
status / blocked_status 400 / 403 the answers
keep_days 30 days a record stays after its block ended; null keeps them
allow [] addresses or ranges never blocked
cloudflare.enabled false (WIREFENCE_CLOUDFLARE) judge Cloudflare's requests by CF-Connecting-IP
cache_store / log_channel null the application's defaults

A caveat for your components

A property typed string bound to something that sends an array, such as a <select multiple> or a group of checkboxes, is a bug in the component, and wirefence will take its requests for probes. Type such properties array.

Tests

The tests post to Livewire's real update endpoint with a real snapshot, forging only what a bot forges.

composer install
vendor/bin/phpunit

Sponsors

wirefence is supported by the following sponsors. Thank you for keeping it growing:

Kenodo Kenodo     AndorraDev AndorraDev

Author

Created by Edu Lazaro

License

wirefence is open-sourced software licensed under the MIT license.