tuijncode/laravel-waf
Laravel WAF middleware that helps protect your application from common web attacks by filtering and blocking suspicious requests before they reach your routes.
The package is a request-filtering WAF that sits in Laravel's HTTP stack, writes findings to a waf_logs table, and (optionally) blocks matched requests. Day one is about proving it is actually inspecting traffic before you trust it.
waf.php) and the signature pack (waf-patterns.php), then run the package migrations. Both published files become your files — the doctor command explicitly checks them for drift against the installed version, so treat republishing as part of every upgrade, not a one-off.php artisan waf:doctor first. This is the entry point, not a troubleshooting afterthought. It verifies the WAF is enabled for the current environment, that waf_logs has every column the writer inserts, that the exclusion table exists, that the middleware is genuinely attached to a route/group/global stack, that your published config and pattern pack aren't stale, and that the cache driver can support flood counting, bans and dedup. Every finding prints the command or setting that fixes it.' OR 1=1, a ../../etc/passwd, a passwd%00.jpg), and confirm rows land in waf_logs. An empty log should mean "no attacks", and step 2 is how you earn the right to believe that.Deploy-time gate. waf:doctor exits non-zero only on real failures, so put it in CI and in your deploy script after migrate. That's the single highest-leverage habit with this package: it converts every silent-failure mode (missing column, stale pattern pack, unattached middleware, unusable cache driver) into a red build instead of an empty log you misread as calm.
Upgrade ritual. New release → composer update → vendor:publish the config/pattern pack → migrate → waf:doctor. The v1.3.0 schema self-reporting exists because people skipped the middle steps: a missing waf_logs column discards every finding, and previously the only trace was a raw SQL error per request. If you see the hourly "schema is stale" message in your logs, that's the package telling you the log is lying to you.
Exclusion management through the log flow. When a finding is a false positive, accept it from the log/dashboard (acceptFromLog) rather than hand-inserting a row. That path stores the exact rule id and is stable across this release. Use exclusions as narrow, per-signature switches — they disable detection for a real attack class, and in blocking mode they disable protection.
Fail2ban integration. php artisan waf:export --format=fail2ban --jail=laravel-waf emits fail2ban-client set <jail> banip <ip> lines for piping into a shell. Because the output is meant to be executed, the jail name is restricted to shell-safe characters — keep jail naming boring (laravel-waf, laravel-waf-admin) and don't try to template it from user input. A typical cron pulls the export and pipes it to sh on the edge host, letting Laravel's flood/ban logic feed your network-level ban instead of duplicating ban storage.
Blocking vs. logging per environment. Enable detection everywhere, enable blocking where you can afford false positives. Combine with the cache-backed flood counting and bans — the doctor validates the cache driver can support them, so pick a driver with real atomic increments (Redis/Memcached) if you're relying on bans. array/file in production is the classic "why does nothing get banned" bug.
Null-byte handling. Rule 920270 flags %00 (or a decoded NUL) in query, path, headers and cookies — the file=passwd%00.jpg extension-check bypass. Request bodies are deliberately exempt because binaries legitimately contain NULs, so don't write an exclusion for 920270 to "fix" a file-upload false positive; the body case is already handled. If you have a legacy endpoint passing NULs in the query string, fix the endpoint or add a tightly-scoped exclusion — not a substring rule.
match_label used to match by substring, so a rule labelled 942 silenced every SQL injection signature and SQL silenced everything mentioning SQL. Matching is now exact: the finding's rule id (942100), its full description, or the whole [942100] … signature string. Audit hand-inserted exclusion rows before upgrading — if any relied on partial matching, they will silently stop excluding. That direction is safe (detection comes back on rather than off), but expect a burst of findings and, if you're in blocking mode, real blocks on traffic you'd previously whitelisted. The old three-character minimum guard is gone; exact matching makes it unnecessary.waf_logs column, missing exclusion table, unattached middleware, stale published pattern pack, unsupported cache driver — all of these present identically as "no findings". Never conclude a WAF is quiet without a recent green waf:doctor.waf-patterns.php once and never again means every signature shipped since then never runs — including 920270 and anything in 1.3.x. The doctor compares the published pack against the installed version; take the drift warning seriously even though it's "just" a config file.waf:doctor → confirm the middleware is in route:list -v/Kernel for the path you're testing → hit the endpoint with a known probe from the same environment → check waf_logs → then look at exclusions. Don't start at the exclusion table; the most common cause of "my rule doesn't fire" is that nothing is firing.vendor:publish + migrate commands. If your alerting only watches for error rate, this will be the only signal you get that every finding is being thrown away.How can I help you explore Laravel packages today?