darvis/livewire-honeypot
darvis/livewire-honeypot provides lightweight honeypot spam protection for Livewire forms, blocking bots without CAPTCHAs or friction for real users. Simple to install and configure, it integrates cleanly with Laravel and existing Livewire components. No JavaScript required. Drop-in protection.
Package profile: A small, single-purpose Laravel/Livewire spam-mitigation package implementing the honeypot technique (a hidden form field that humans don't fill and naive bots do). No external service, no data store, no credentials, no network egress.
Architecture fit
validate() call for server-side enforcement, and optional config/views.Integration feasibility
composer require, add <x-honeypot /> to forms, call validate() (plain forms) or validateHoneypot() (Livewire components) in the submit path.Technical risk
ConvertEmptyStringsToNull middleware, every genuine plain-form submission was rejected as spam: the middleware coerced the empty hidden field to null, and validate() interpreted null as "field absent" rather than "field present but empty." The 1.5.1 documentation shipped a manual array_map(fn ($value) => $value ?? '', ...) workaround, which tells us the defect was known and documented before it was fixed. If any product surface uses this package on plain forms and was integrated against an older version, assume conversion loss occurred and validate against analytics before and after.<x-honeypot wire:model="..." /> bound to a non-default property was never actually validated — validateHoneypot() unconditionally read hp_website. Forms using the attribute form of the component were unprotected. This is a security-relevant fix with no automatic migration: each affected view must pair the wire:model path with matching validateHoneypot(model: ..., errorKey: ...) arguments, and the bound property must start as an empty string. Any team that assumed coverage from the presence of the component should treat prior "no spam" telemetry as unverified.HasHoneypot on a Livewire form object or any non-component class now throws LogicException from validateHoneypot() with a directive message. This only surfaces configurations that were already 100% broken (no mount hook → no start time → every submit rejected), so it converts a silent outage into a diagnosable exception. Net positive, but it is an uncaught-exception risk on any submit path that reaches production untested.withoutMiddleware() — a direct response to risk #1. The prior absence of such a test is the underlying quality concern: unit tests that bypass middleware cannot catch middleware-interaction bugs, which is exactly the class of bug that produced a full-fidelity outage.Key questions
ConvertEmptyStringsToNull rejection and the Livewire wire:model no-op? Is there a published advisory/CVE, or only release notes?validate() still documented as the primary plain-form entry point, and does the new middleware-stack test cover both passing and failing cases (present-but-null, absent, filled)?model argument a literal property path string rather than a bound value, and does it follow nested/wire:model.live binding semantics?aria-hidden, tabindex="-1", and autocomplete="off" so it is not surfaced to screen readers or password managers?Stack fit
validate() before persisting.Migration path
array_map(fn ($value) => $value ?? '', $request->all()) workaround if it was applied; leaving it is harmless but it is dead code that obscures intent. Confirm validate($request->all()) matches documented usage.<x-honeypot wire:model=. For each hit, pass the identical path and error key into the component call — $this->validateHoneypot(model: 'contact.hp_website', errorKey: 'contact.hp_website') — and verify the bound property is initialized to ''. Treat each hit as a security finding until the pairing is verified in code review, not just by grep-and-edit.<x-honeypot /> requires no change; validateHoneypot() with no arguments preserves prior behavior, so untouched call sites are backward compatible.LogicException appears, move the trait and the validateHoneypot() call from the form object to the Livewire component. Budget for this only if the exception actually surfaces — it indicates a previously broken integration, so treat it as a bug fix, not a regression.Compatibility
validateHoneypot(?string $model, ?string $errorKey) arguments preserve existing call signatures; components that define their own validateHoneypot() keep working. The only non-additive change is the LogicException, which affects code paths that were already non-functional.Sequencing
wire:model honeypot usages; remediate and review the pairs.LogicException sites found in staging smoke tests.Maintenance
Support
LogicException.LogicException message is self-describing ("move the trait to the component"), which lowers escalation cost considerably compared with the prior silent behavior.wire:model variant should be handled through the normal vulnerability workflow (owner, ticket, verification), not as routine maintenance.Scaling
Failure modes
wire:model case. Only detectable with a negative test (assert a filled hidden field is rejected) per form; make that assertion part of the form's test suite.LogicException: a 500 on submit for misconfigured components. Surfaces immediately in staging smoke tests; low production risk if submit paths are exercised pre-deploy.Ramp-up
wire:model-bound components.How can I help you explore Laravel packages today?