rennf93/laravel-guard
Laravel Guard is a lightweight package for adding configurable guard-based access control to Laravel apps. It integrates with Laravel’s authentication system, making it easy to define guards, manage providers, and protect routes with simple configuration.
Install the adapter and let Composer pull the engine floor:
composer require rennf93/guard-laravel
This release requires rennf93/guard-core-php ^4.4.0; do not pin an older engine, because GuardEngine::applyResponseModifier() and the 4.4.0 config validation are required.
Start with the two places that drive day-to-day use:
docs/usage.md, especially the new agent and dynamic-rule sections.SecurityConfig and middleware registration.The first practical use case is adding GuardMiddleware to a route group and confirming that normal Laravel responses pass through unchanged:
Route::middleware('guard')->group(function () {
Route::get('/account', AccountController::class);
Route::post('/login', LoginController::class);
});
Then add blocking behavior, excluded paths, and a customResponseModifier if you need to stamp headers or adjust status codes. The modifier now applies to pass-through responses too, not only responses produced by the guard translator, so it is the right hook for consistent security headers across blocked and allowed responses.
Protect broadly, exclude narrowly. Apply the middleware at the web or api group level, then use excluded paths for health checks, webhooks, and assets. Because the modifier now runs on excluded-path responses as well, you can rely on it for uniform headers even where checks are bypassed.
Use customResponseModifier as the response policy layer. It runs once, after the engine security headers and before the CORS verdict headers. Typical uses:
Keep the modifier defensive. A modifier that throws leaves the response unmodified, and Set-Cookie is not exposed to it.
Wire the agent explicitly. Agent-related features are documented in this release and listed under suggest. A typical setup enables the agent first, then dynamic rules and enrichment:
$config = new SecurityConfig(
enableAgent: true,
// agent handler / lifecycle hooks as documented
enableDynamicRules: true,
dynamicRuleInterval: 60,
enableEnrichment: true,
);
enableDynamicRules and enableEnrichment now require enableAgent: true. If either is set without the agent, validation fails when the engine is built.
Treat dynamic rules as overlays, not patches. Each new dynamic-rule version applies over the base config and does not carry forward the previous version's changes. If you need a temporary ban, use an ip_blacklist dynamic rule and expect it to behave as a timed ban for ipBanDuration seconds rather than a permanent merge into the static blacklist.
Be deliberate with IP lists and proxies. Global and route-level precedence follows the engine reference:
If you run behind a proxy or load balancer, configure trustedProxies carefully. Client IP canonicalization now drops ports from X-Forwarded-For, stops trusting a mapped peer inside an IPv4 CIDR of trustedProxies, and canonicalizes list entries during matching.
Use the scan budget as a safety valve. detectionMaxScanSeconds defaults to 10.0 and accepts 0.1 to 60.0. It caps regex scan CPU time per request. If the budget is exhausted, remaining patterns are skipped and the request records a weighted pattern_timeout threat. This is useful for keeping expensive requests from consuming unbounded CPU, but it can also change detection outcomes.
Handle all Laravel response types naturally. GuardMiddleware::handle() and finish() now type against Symfony\Component\HttpFoundation\Response. A downstream JsonResponse, RedirectResponse, or StreamedResponse no longer causes a TypeError. Blocked responses still come from the translator as Illuminate\Http\Response, so existing callers that receive blocks are unaffected.
Upgrade the engine with the adapter. This release floors guard-core-php at ^4.4.0. Older engines do not have applyResponseModifier() or the 4.4.0 config validation.
Agent options require the agent. Set enableAgent: true and provide an agent handler before enabling dynamic rules or enrichment. Without it, SecurityConfig no longer validates and the engine throws when built.
Dynamic rule changes do not stack. Do not assume a new dynamic version inherits the previous version's modifications. Each version is applied over the base config.
An ip_blacklist dynamic rule is a timed ban. It is not merged into the static blacklist. Use it when you want a temporary ban for ipBanDuration seconds.
IP list precedence is strict. A whitelist alone decides. The blacklist is only consulted when no whitelist is configured. At the route level, a route whitelist skips the global lists, and a route blacklist is only consulted when the route has no whitelist.
IP canonicalization can change matches. X-Forwarded-For ports are stripped, mapped peers inside IPv4 CIDRs of trustedProxies are no longer trusted, and matching canonicalizes list entries. A ban, rate limit, or list entry may now match a client it previously did not.
Watch the passive-mode log format if you parse logs. The suspicious log line without a trigger now reads:
[PASSIVE MODE] Suspicious activity detected from <ip>: ... - Reason: <reason> - Headers: ...
Update any log-based alerts or dashboards that match the old wording.
detectionMaxScanSeconds can create new findings. A request that previously scanned all patterns may now be flagged as pattern_timeout once the budget is exhausted. Tune the value if you see unexpected weighted threat records.
Response modifier limits. It cannot read Set-Cookie, it cannot read a body that is not string-castable, and an unchanged header keeps its original value list. If the modifier throws, the response is left unmodified. For responses the engine already modified, the modifier is not applied twice.
Custom middleware signatures. If your own code wraps or type-hints the guard middleware, prefer Symfony\Component\HttpFoundation\Response rather than Illuminate\Http\Response; Laravel handlers can return JSON, redirects, and streamed responses that are not Illuminate\Http\Response.
Debugging order. For unexpected block/pass behavior, check middleware ordering, excluded paths, route-level lists, trustedProxies, and whether an agent-driven dynamic rule is active. For unexpected response headers or status, check customResponseModifier before looking at the downstream controller.
How can I help you explore Laravel packages today?