Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Bot Shield Laravel Package

marshmallow/bot-shield

marshmallow/bot-shield provides lightweight bot protection for Laravel applications. Use configurable middleware and detection rules to identify and block suspicious automated requests, reduce spam, and protect forms and endpoints without complex setup.

View on GitHub
Deep Wiki
Context7

Getting Started

Bot Shield sits between Livewire's exception handling and your error reporting. Its job is to keep forged/probe traffic from turning into 500s, Sentry noise, and alert fatigue, while leaving genuine bugs untouched.

Minimal path:

  1. composer require marshmallow/bot-shield
  2. php artisan bot-shield:install — this publishes config/bot-shield.php.
  3. Open that file and read exceptions.rules first. It is the whole package in one array: a list of exception classes, optional contains string matchers, and (as of v1.2.0) optional when conditions.
  4. Skim the Hardening\Conditions namespace — ForgedMethodName and ForgedReactivePropUpdate are the new v1.2.0 conditions and are the clearest examples of the pattern.

First use case: someone (or something) POSTs to /livewire/update with a manipulated snapshot — an array where a scalar belongs, a calls entry with a non-identifier method name, or an updates entry mutating a reactive prop it doesn't own. Today that surfaces as a 500 and an exception report. After install, the request is answered with a client error and nothing is reported — but only if the request looks forged, not merely because the user agent is odd.

Implementation Patterns

Rule shape. Each entry in exceptions.rules is roughly ['class' => ..., 'contains' => [...], 'when' => ConditionClass::class, 'always' => bool]. contains narrows by message fragment; when requires a condition class to also accept the request; always skips the user-agent gate entirely.

Prefer conditions over message matching. Message fragments are brittle across PHP and Livewire versions. The v1.2.0 additions show the better path: MethodNotFoundException is only suppressed when ForgedMethodName confirms the called method isn't a valid identifier — a typo in a real method name still reports. Same for CannotMutateReactivePropException + ForgedReactivePropUpdate: only suppressed when the request's own updates touch that prop on that component. Write your own condition classes implementing ExceptionCondition whenever you catch yourself reaching for a broad contains.

Use contains only as a narrow pair. Note the shape of the new forged-array rules: ['class' => ErrorException::class, 'contains' => ['Attempt to read property', 'on array']] — two fragments that must both be present. Never register Error or TypeError on the class alone; you will swallow real bugs.

Layer the response. Suppression and response are separate concerns in practice. Decide per rule whether you want "don't report" or "don't report and answer a client error." Forged probes deserve a fast 4xx; ambiguous cases are often better silently unreported so the normal handler still shapes the response.

Test the gate, not just the rule. Write feature tests that hit livewire/update with a forged payload (array into a scalar prop, non-identifier method name) and assert no exception was reported; then a test with a genuine typo and assert it was reported. The second half is the one people forget, and it's the one that catches over-broad rules.

Environment control. BOT_SHIELD_FORGED_UPDATES toggles forged-update detection. This is the pattern to copy for anything that alters failure semantics: default on, env-off for the one deploy where you need to bisect.

Gotchas and Tips

Publishing replaces, it does not merge. This is the headline upgrade trap. A published exceptions.rules array fully replaces the package defaults, so every new rule in v1.2.0 is inert on an existing install until you paste it in yourself. After any Bot Shield upgrade, diff the package's default config against yours. If you maintain a long-lived published config, consider keeping it thin and only overriding the entries you actually need.

A missing key means "on". exceptions.forged_updates is treated as enabled when absent. So forged-update detection cannot be turned off with BOT_SHIELD_FORGED_UPDATES=false unless the key exists in your config. Add 'forged_updates' => env('BOT_SHIELD_FORGED_UPDATES', true), explicitly — otherwise you'll spend an afternoon wondering why the env var does nothing.

when conditions still run alongside the detector. The condition is an additional gate, not a replacement, unless you also set always => true. The two v1.2.0 Livewire rules use always, which is deliberate: a forged method name is forged regardless of user agent. Don't set always casually on anything message-matched.

Livewire class availability. MethodNotFoundException and CannotMutateReactivePropException live in Livewire internals and can move or disappear between Livewire majors. Registering a rule for a class that doesn't exist is usually harmless but silently useless — verify after upgrading Livewire, and pin your Livewire constraint if you depend on these rules.

Exception class drift across PHP versions. ErrorException vs Error vs TypeError for the "on array" family varies with PHP version and error handler setup. Confirm which class you're actually getting before writing the rule; the contains fragments are the stable part.

Silent suppression hides config bugs. Because Bot Shield removes reporting, a misconfigured rule looks identical to a rule that's working. Log locally at debug level when a rule fires during rollout, or temporarily point the exception at a local sink, so you can see matches before trusting them in production.

Extension point to lean on. Custom ExceptionCondition implementations are the sanctioned way to encode "is this actually forged?" logic. Keep them request-pure and cheap — they run inside exception handling, where a slow condition turns a 500 into a timeout.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings