mchev/banhammer
Laravel package for banning users, IP addresses, and other entities with a polymorphic ban model, middleware, events, and helper methods. Supports temporary or permanent bans, reasons, and easy integration into existing applications.
Architecture fit. mchev/banhammer is a self-contained abuse-moderation layer for Laravel: a bans table plus a Bannable trait on user models, a Banhammer facade for creating/lifting bans (user, IP, email), route middleware that rejects banned actors early in the request lifecycle, cached ban lookups to keep the check off the hot path, and lifecycle events for banned/unbanned transitions. Architecturally it is a cross-cutting dependency rather than a service dependency — it inserts into the HTTP kernel's middleware stack, the auth/user model, and the cache store at the same time. That makes it cheap to adopt and moderately invasive to remove. For a product where account/IP gating is a first-class requirement (trust & safety, fraud, free-tier abuse, credential-stuffing response), the fit is strong. Where moderation is an edge case, a lighter in-house guard is usually preferable to a package that owns the middleware stack.
Integration feasibility. The integration surface is small and conventional for Laravel: publish config + migration, run migration, add trait, register middleware alias, wire a cache store. No external services, no queue workers required, no vendor account. A competent Laravel engineer can have a working end-to-end ban flow in under a day, including tests. The real feasibility question is not "can we install it" but "does our ban policy map onto its model" — scope (user vs. IP vs. email vs. composite), duration/expiry semantics, and whether the IP check keys off REMOTE_ADDR or a proxy header behind our load balancer.
Technical risk. v2.5.0 is a compatibility-only release — Laravel 13.x support and PHP 8.5 in the test matrix. Two readings matter, and we must confirm which is true from the diff:
No deprecations, no API changes, and no behavioural changes are disclosed in the release notes, so the package's own contract appears stable. The residual risks are (a) single-maintainer project — bus factor and CVE response latency; (b) the middleware sits in front of application logic, so a bug or a stale cache produces a fail-closed lockout of legitimate users, which is a customer-visible outage, not a degraded feature; (c) the ban check depends on the cache store, making ban propagation latency a configuration property of our infrastructure.
Key questions.
TrustProxies/X-Forwarded-For, or only the socket address?Stack fit. Purpose-built for Laravel on Eloquent and the native cache/middleware abstractions — no framework impedance. It composes cleanly with existing auth (Sanctum/session), rate limiting, and observability, because it is one middleware and one table rather than a parallel policy engine. It is complementary to, not a substitute for, Laravel's rate limiter or a WAF: rate limiting throttles, a WAF blocks at the edge, Banhammer enforces persistent identity-level bans inside the app.
Migration path. Greenfield: composer require, publish config and migration, migrate, add trait, register middleware alias, apply to the route groups that need gating. Brownfield with an existing ban mechanism (a banned_at column, a status enum, or manual IP denylists): run both in parallel behind a feature flag, backfill the bans table from the legacy source in an idempotent seeder, then cut the enforcement point over to the middleware and retire the legacy check in a second release. Do not run two enforcement layers indefinitely — divergent ban state is how "banned but logged in" incidents happen.
Compatibility. The headline is Laravel 13.x plus PHP 8.5 test coverage. Before we commit: pin the exact Laravel and PHP versions we test against, confirm the constraint set against our other first-party packages (auth, admin panels, and anything that also touches the user model), and verify the migration is additive (new table only) so it is safe to run on a live database. If v2.5.0 raises the Laravel floor, treat it as gated on our own framework upgrade and slot it after, not before.
Sequencing.
Maintenance. Low surface area: one table, one config file, one middleware alias. The upgrade treadmill is the real cost — the package must track Laravel majors, so it becomes a line item in every framework upgrade, which v2.5.0 demonstrates (Laravel 13 compat shipped as its own release). Budget a small, recurring review per Laravel major rather than a large one-off effort. Single-maintainer risk is real; keep the integration shallow enough that forking or replacing the middleware with an in-house equivalent is a day of work, not a quarter.
Support. Enforcement failures arrive as user-facing "you are blocked" reports routed to support, not as clean package errors. Support needs a lookup path from a user report to ban record (reason, issuer, expiry) and a documented one-click unban. Without admin tooling, every ban appeal becomes an engineering ticket. Also confirm the package's error paths: does a banned user get a 403, a redirect, or a custom view, and is that message something we are willing to show to a paying customer?
Scaling. Ban writes are rare; ban checks are per-request and sit on the critical path. The cache-backed IP blocklist is what makes this viable — a per-request database read against bans under peak load is the failure mode to design out. Verify the blocklist is loaded into a fast store (Redis), sized sanely, and refreshed on a predictable interval. At high ban volumes the blocklist itself becomes a hot key; plan for sharded or per-scope keys if ban counts reach the tens of thousands.
Failure modes.
Ramp-up. An engineer familiar with Laravel can be productive in half a day; the package's concepts (trait, middleware, facade, cache) are all native idioms. The learning cost sits in policy, not code: defining ban scope and duration, appeal handling, and the fail-open/fail-closed decision. Document those decisions in the runbook at adoption time — they are the things someone will need at 3 a.m., and they will not be found in the package README.
How can I help you explore Laravel packages today?