bdsa / wafy
A Laravel package to automatically ban IP addresses and detect malicious requests.
Requires
- php: >=7.4
- illuminate/support: ^8.0|^9.0|^10.0|^11.0|^12.0
Requires (Dev)
- orchestra/testbench: ^6.0|^7.0|^8.0|^9.0
- phpunit/phpunit: ^9.0|^10.0
README
Wafy is a robust Laravel package developed by Bdsa designed to automatically ban IP addresses and detect malicious requests, including SQL Injection, XSS, and more.
Features
- 🛡️ IP Banning: Automatically block IPs engaging in suspicious activity.
- 🕵️ Malicious Request Detection: Detects SQLi, XSS, LFI, and RCE attempts.
- ⏱️ Temporary & Permanent Bans: Configurable ban durations.
- ⚙️ Customizable Patterns: Define your own regex patterns for detection.
- 🖥️ Artisan Commands: Easily manage banned IPs via CLI.
Installation
1. Require with Composer
Add the package to your project:
composer require bdsa/wafy
2. Publish Configuration
Publish the configuration file and migrations:
php artisan vendor:publish --provider="Bdsa\Wafy\WafyServiceProvider"
3. Run Migrations
Create the banned_ips table:
php artisan migrate
Usage
Middleware
Wafy provides two key middlewares : BlockBannedIp & DetectMaliciousRequests.
Protecting Routes
Apply the middleware to your routes or groups:
use Bdsa\Wafy\Middleware\BlockBannedIp; use Bdsa\Wafy\Middleware\DetectMaliciousRequests; Route::group(['middleware' => ['block.banned.ip', 'detect.malicious.requests']], function () { Route::get('/', function () { return view('welcome'); }); // Your protected routes });
Artisan Commands
Manage banned IPs directly from the terminal:
-
Ban an IP manually:
php artisan wafy:ban {ip_address} [--reason="Your reason"] -
Unban an IP:
php artisan wafy:unban {ip_address} -
List all banned IPs:
php artisan wafy:list
-
Enable/Disable WAF:
php artisan wafy:mode {enable|disable} -
Set Action Mode (Block or Log-Only):
php artisan wafy:action {block|log}
ℹ️ Note —
wafy:modeandwafy:actionare temporary runtime overrides. These two commands store their state in the cache, so they are meant for momentary situations (testing, incident response). Any cache flush (php artisan cache:clear,config:cache, a deploy, a Redis restart…) resets them, and Wafy falls back to the values inconfig/wafy.php. To change the behaviour permanently, editconfig/wafy.php(or the matchingWAFY_*environment variables) — that is the source of truth.
⚠️ Running behind a reverse proxy / CDN (read this first)
Wafy identifies clients by IP ($request->ip()) and can ban them. If your
application runs behind a reverse proxy, load balancer or CDN (Nginx, Traefik,
Cloudflare, AWS ALB…), you must configure Laravel's TrustProxies
middleware so that $request->ip() returns the real client IP.
- If you don't configure trusted proxies, every request appears to come from the proxy. The first malicious request then bans your own proxy, cutting off all traffic.
- If you trust proxies with a blanket
*, theX-Forwarded-Forheader becomes attacker-controlled: an attacker can spoof a clean IP to bypass bans, or forge a victim's IP to get it banned. Only trust the specific proxy ranges you use.
Also add your proxy / CDN ranges and any critical infrastructure to
wafy.allowed_ips (CIDR ranges are supported) so they can never be banned.
Configuration
The configuration file is located at config/wafy.php. Besides the detection
patterns, the following options control how bans are applied:
| Key | Default | Description |
|---|---|---|
ban_threshold |
1 |
Number of detections from one IP (within strike_window minutes) before it is banned. Raising this above 1 is strongly recommended so a single false positive doesn't lock out a legitimate (shared/NAT/mobile) IP. The offending request is always blocked regardless. |
strike_window |
60 |
Minutes over which strikes accumulate. |
ban_duration |
1440 |
Automatic ban lifetime in minutes (24h). Set to null for permanent bans. Manual wafy:ban bans are always permanent. |
max_scan_length |
16384 |
Max characters inspected per field — caps regex CPU cost (ReDoS protection). |
fail_open |
true |
If the ban database is unreachable, let requests through (true) instead of returning 503 for everyone (false). |
scan_headers |
['User-Agent', 'Referer'] |
Request headers inspected for patterns. |
sensitive_keys |
passwords, tokens, card fields… | Input keys whose values are redacted before a request is stored or notified. |
allowed_ips |
[] |
IPs / CIDR ranges (IPv4 & IPv6) that bypass Wafy entirely. |
Default protection covers:
- SQL Injection (SQLi):
UNION SELECT, common SQL verbs, hex encoding. - Local File Inclusion (LFI): Directory traversal (
../), system files (/etc/passwd). - Cross-Site Scripting (XSS): Script tags, event handlers (
onload,onerror). - Remote Code Execution (RCE): Shell commands (
cat,wget), PHP execution functions.
Example config/wafy.php:
return [ 'enabled' => env('WAFY_ENABLED', true), 'patterns' => [ '/(union(\s+all)?\s+select)/i', '/(select\s+.*\s+from|delete\s+from|update\s+.*\s+set)/i', '/(<script.*?>.*?<\/script>)/is', // Add your custom patterns here ], 'allowed_ips' => [ '127.0.0.1', // Localhost '192.168.1.1', // Office IP ], 'notifications' => [ 'enabled' => env('WAFY_NOTIFICATIONS_ENABLED', false), 'channels' => ['mail'], // Choose 'mail', 'slack' or both 'email' => env('WAFY_NOTIFICATION_EMAIL', 'admin@example.com'), 'slack_webhook' => env('WAFY_SLACK_WEBHOOK', ''), ], ];
Testing
To run the package tests:
vendor/bin/phpunit
License
This project is licensed under the MIT License.