rilo-arbabillah / laravel-crowdsec
A lightweight CrowdSec-like WAF protection package for Laravel
Requires
- php: ^8.1
- illuminate/console: ^10.0|^11.0|^12.0
- illuminate/contracts: ^10.0|^11.0|^12.0
- illuminate/database: ^10.0|^11.0|^12.0
- illuminate/http: ^10.0|^11.0|^12.0
- illuminate/support: ^10.0|^11.0|^12.0
Requires (Dev)
- guzzlehttp/guzzle: ^7.10
- guzzlehttp/psr7: ^2.9
- orchestra/testbench: ^8.0|^9.0|^10.0
- phpstan/phpstan: ^1.12
- phpunit/phpunit: ^10.0|^11.0
This package is auto-updated.
Last update: 2026-04-20 03:16:18 UTC
README
A lightweight, CrowdSec-like Web Application Firewall (WAF) protection package for Laravel applications. This package provides real-time threat detection and IP blocking based on WAF patterns and behavior analysis.
Release Status
- Latest stable Packagist release:
v1.0.2 - Stable compatibility: Laravel
^10.0|^11.0|^12.0with the current hardening and CI updates
Features
- WAF Pattern Detection: Detects SQL injection, XSS, path traversal, command injection, and 11 more attack types
- IP Blocking: Temporary IP blocks with automatic expiration and progressive escalation
- Behavior-based Protection: Rate limiting, brute-force detection, and threat score tracking with auto-decay
- Caching Layer: Cached blocked IP lookups for high-traffic applications
- Event System: 4 Laravel events (ThreatDetected, IpBlocked, IpUnblocked, BehaviorThresholdExceeded)
- Notifications: Email and Slack alerts with severity filtering and rate limiting
- Honeypot Routes: Trap routes to catch automated scanners
- Per-Route Rate Limiting: Configurable rate limits via middleware (
crowdsec.rate:60,1) - Custom Patterns: Register custom detection scenarios at runtime
- GeoIP Lookup: IP geolocation via ip-api.com with caching
- REST API: 6 API endpoints for programmatic management (block, unblock, check, stats, events, blocked)
- Admin Dashboard: Standalone dark theme Blade dashboard (enable via config)
- SIEM Export: Export events in JSON, CSV, or Syslog (RFC 5424) format
- CLI Commands: Statistics, cleanup, and export utilities
- Facade API: Easy programmatic access to all features
- Auto-migrations: Database tables created automatically
- CI Pipeline: GitHub Actions with PHP 8.1/8.2/8.3 × Laravel 10.x/11.x/12.x plus a PHPStan quality gate
Requirements
- PHP ^8.1
- Laravel ^10.0 or ^11.0 or ^12.0
- MySQL/PostgreSQL/SQLite (any Laravel-supported database)
Installation
Install the package via Composer:
composer require rilo-arbabillah/laravel-crowdsec
The package will automatically register its service provider and facade.
Configuration
Publish the configuration file to customize detection scenarios and thresholds:
php artisan vendor:publish --tag=crowdsec-config
This will create config/crowdsec-scenarios.php where you can:
- Enable or disable the package
- Configure detection patterns for each attack type
- Adjust behavior thresholds (request limits, 404 limits, login attempts)
- Set block durations per severity
- Whitelist IPs that should never be blocked
Notification Channels
CrowdSec can send on-demand security alerts through email and Slack. Mail delivery uses
the notifications.recipients list, while Slack delivery requires an incoming webhook URL.
'notifications' => [ 'enabled' => env('CROWDSEC_NOTIFY_ENABLED', false), 'channels' => ['mail', 'slack'], 'severity_threshold' => 'high', 'rate_limit_minutes' => 5, 'recipients' => ['security@example.com'], 'slack_webhook_url' => env('CROWDSEC_NOTIFY_SLACK_WEBHOOK_URL', ''), ],
CROWDSEC_NOTIFY_ENABLED=true CROWDSEC_NOTIFY_CHANNELS=mail,slack CROWDSEC_NOTIFY_RECIPIENTS=security@example.com,ops@example.com CROWDSEC_NOTIFY_SLACK_WEBHOOK_URL=https://hooks.slack.com/services/T000/B000/XXXX
If you enable the slack channel without a webhook URL, php artisan crowdsec:doctor
will report the configuration as invalid.
Enabling/Disabling the Package
You can toggle the package on or off via configuration:
// config/crowdsec-scenarios.php return [ // Enable or disable the entire package 'enabled' => true, // Set to false to disable all protection // ... rest of configuration ];
When disabled, the middleware will pass all requests through without any filtering or blocking. This is useful for:
- Development environments where you need to test without WAF interference
- Debugging specific issues without protection blocking legitimate traffic
- Temporary maintenance windows
Default Configuration
// config/crowdsec-scenarios.php return [ // Enable/disable the package 'enabled' => true, // Whitelist IPs (won't be blocked) 'whitelist_ips' => [ '127.0.0.1', '::1', ], // Behavior thresholds 'behavior' => [ 'request_threshold' => 500, // requests per minute '404_threshold' => 15, // 404s per minute 'login_threshold' => 5, // login attempts per minute 'threat_score_threshold' => 50, 'block_duration' => 240, // 4 hours 'severity' => 'high', ], // Block duration defaults (in minutes) 'defaults' => [ 'low' => 60, 'medium' => 240, 'high' => 720, 'critical' => 1440, ], // ... detection patterns ];
Basic Usage
Applying Middleware to Routes
Apply the middleware to individual routes:
use Illuminate\Support\Facades\Route; Route::middleware(['crowdsec'])->group(function () { Route::get('/admin', function () { // Protected route }); Route::post('/login', [AuthController::class, 'login']); });
Applying Middleware Globally
Add the middleware to your HTTP kernel for global protection:
// app/Http/Kernel.php protected $middlewareAliases = [ // ... 'crowdsec' => \RiloArbabillah\LaravelCrowdSec\Http\Middleware\CrowdSecProtection::class, ];
Then apply to routes or route groups:
// Protect all web routes Route::middleware(['crowdsec'])->group(base_path('routes/web.php')); // Protect API routes Route::middleware(['api', 'crowdsec'])->group(base_path('routes/api.php'));
Skipping Middleware for Certain Routes
// Disable for health check endpoints Route::middleware(['crowdsec'])->group(function () { Route::get('/admin', function () { // Protected }); }); Route::get('/health', function () { // Not protected })->withoutMiddleware(['crowdsec']);
Programmatic Usage
Use the CrowdSec facade for programmatic control:
Check if IP is Blocked
use RiloArbabillah\LaravelCrowdSec\Facades\CrowdSec; $ip = request()->ip(); if (CrowdSec::isBlocked($ip)) { abort(403, 'Your IP has been blocked'); }
Manually Block an IP
use RiloArbabillah\LaravelCrowdSec\Facades\CrowdSec; // Block for 60 minutes CrowdSec::blockIp($request->ip(), 'Manual ban - spam', 60); // Block for 24 hours (default for critical threats) CrowdSec::blockIp($request->ip(), 'Suspicious activity', 1440);
Unblock an IP
use RiloArbabillah\LaravelCrowdSec\Facades\CrowdSec; CrowdSec::unblockIp($ip);
Track Login Attempts
Call this after failed login attempts for brute-force protection:
use RiloArbabillah\LaravelCrowdSec\Facades\CrowdSec; public function login(Request $request) { if (! Auth::attempt($credentials)) { // Track failed attempt CrowdSec::trackLoginAttempt($request->ip()); return back()->withErrors(['email' => 'Invalid credentials']); } // Reset login attempts on successful login // (optional - you could implement this) return redirect('/dashboard'); }
Analyze Request for Threats
use RiloArbabillah\LaravelCrowdSec\Facades\CrowdSec; $threats = CrowdSec::analyzeRequest($request); if (! empty($threats)) { foreach ($threats as $threat) { \Log::warning('Security threat detected', [ 'type' => $threat['type'], 'severity' => $threat['severity'], 'matched' => $threat['matched'], ]); } }
CLI Commands
View Protection Statistics
php artisan crowdsec:stats
Output includes:
- Active blocked IPs
- Expired blocked IPs
- Events today
- Events this week
- Top attackers (IP addresses)
For JSON output (useful for monitoring):
php artisan crowdsec:stats --json
Clean Up Expired Bans
Remove expired IP blocks and old security events:
php artisan crowdsec:cleanup
If audit logging is enabled, the cleanup command also prunes audit records older than
audit.retention_days.
Cleanup Options
# Preview what would be deleted (no changes) php artisan crowdsec:cleanup --dry-run # Clean only expired bans php artisan crowdsec:cleanup --expired # Clean only old events (older than 30 days) php artisan crowdsec:cleanup --old-events # Clean only old behaviors (older than 7 days) php artisan crowdsec:cleanup --old-behaviors
Audit log retention is configured in config/crowdsec-scenarios.php:
'audit' => [ 'enabled' => env('CROWDSEC_AUDIT_ENABLED', false), 'retention_days' => env('CROWDSEC_AUDIT_RETENTION_DAYS', 365), ],
Automated Cleanup in Production
Add to your routes/console.php or set up a scheduled command:
// In routes/console.php Artisan::command('crowdsec:daily-cleanup', function () { $this->call('crowdsec:cleanup', ['--expired' => true]); })->purpose('Clean up expired bans daily');
Or use Laravel's scheduler:
// In app/Console/Kernel.php protected function schedule(Schedule $schedule) { $schedule->command('crowdsec:cleanup --expired')->daily(); }
Database
The package creates three tables automatically via migrations:
| Table | Description |
|---|---|
blocked_ips |
Tracks blocked IPs with expiration and reason |
ip_behaviors |
Tracks per-IP metrics (request count, 404s, login attempts, threat score) |
security_events |
Logs all detected security threats |
Tables are created when you run:
php artisan migrate
Detected Threats
The package detects the following attack types:
| Threat Type | Severity | Examples |
|---|---|---|
| SQL Injection | Critical | UNION SELECT, OR 1=1, xp_cmdshell |
| XSS | High | <script>, javascript:, onclick= |
| Path Traversal | Critical | ../, %2e%2e%2f |
| Command Injection | Critical | ;cat, |whoami, `id` |
| File Inclusion | High | php://input, data:text/html |
| PHP Serialization | Critical | O:16:"MaliciousClass" |
| Directory Bruteforce | Medium | .git/config, .env, wp-admin |
| Header Injection | High | CRLF injection, Location: |
| Suspicious User Agent | Medium | sqlmap, nmap, python-requests |
| Behavior Threshold | High | Rate limiting, brute-force |
Production Guidelines
1. Monitor Regularly
Run stats command periodically or set up monitoring:
# Check for active threats php artisan crowdsec:stats # Export to monitoring system php artisan crowdsec:stats --json | jq '.events_today'
2. Tune Thresholds for Production
Adjust config/crowdsec-scenarios.php based on your traffic:
'behavior' => [ 'request_threshold' => 1000, // Increase for high-traffic sites '404_threshold' => 20, // Adjust based on your 404 rate 'login_threshold' => 3, // Stricter for login pages 'threat_score_threshold' => 50, 'block_duration' => 240, ],
3. Protect Optional Endpoints Before Enabling Them
The REST API, metrics endpoint, and dashboard are disabled by default. When you enable them, CrowdSec now defaults to authenticated middleware so they are not exposed publicly by accident.
'api' => [ 'enabled' => env('CROWDSEC_API_ENABLED', false), 'middleware' => ['api', 'auth:sanctum'], ], 'metrics' => [ 'enabled' => env('CROWDSEC_METRICS_ENABLED', false), 'path' => 'crowdsec/metrics', 'middleware' => ['web', 'auth'], ], 'dashboard' => [ 'enabled' => env('CROWDSEC_DASHBOARD_ENABLED', false), 'path' => 'crowdsec', 'middleware' => ['web', 'auth'], ],
Recommended production overrides:
'api' => [ 'enabled' => true, 'middleware' => ['api', 'auth:sanctum'], ], 'metrics' => [ 'enabled' => true, 'middleware' => ['signed'], ], 'dashboard' => [ 'enabled' => true, 'middleware' => ['web', 'auth', 'verified'], ],
Run php artisan crowdsec:doctor after enabling any of these surfaces. The doctor
command now fails if the API or dashboard lacks authentication, or if metrics are
exposed without auth or signed middleware.
4. Whitelist Internal Services
'whitelist_ips' => [ '127.0.0.1', '::1', '10.0.0.0/8', // Internal network '192.168.0.0/16', // Internal network 'your-load-balancer-ip', ],
5. Set Up Log Monitoring
Monitor Laravel logs for CrowdSec warnings:
// Log channel configuration 'log' => [ 'driver' => 'daily', 'path' => storage_path('logs/laravel.log'), 'level' => 'warning', ],
6. Schedule Regular Cleanup
// In app/Console/Kernel.php protected function schedule(Schedule $schedule) { // Clean expired bans daily at 3 AM $schedule->command('crowdsec:cleanup --expired --old-events --old-behaviors') ->daily() ->at('03:00'); }
7. Performance Considerations
- The middleware runs on every request - keep patterns optimized
- IP whitelist is checked first for performance
- Behavior tracking uses efficient increment operations
- Consider caching blocked IPs for very high-traffic sites
Performance Benchmarks
The package includes a benchmark suite (tests/Benchmark) to verify overhead stays minimal.
Run benchmarks:
vendor/bin/phpunit tests/Benchmark --testdox
Typical results (100 iterations average):
| Operation | Avg Time | Target |
|---|---|---|
| Whitelist bypass | ~0.006ms | < 0.5ms ✅ |
| Clean request analysis | ~0.038ms | < 2ms ✅ |
| Blocked IP check | ~0.045ms | < 2ms ✅ |
| Threat detection (SQLi) | ~0.047ms | < 5ms ✅ |
| Multi-threat detection | ~0.051ms | < 5ms ✅ |
| POST body analysis | ~0.159ms | < 3ms ✅ |
| Behavior tracking | ~0.163ms | < 3ms ✅ |
Note: Results may vary depending on hardware. Benchmarks use SQLite in-memory.
Contributing
Contributions are welcome! Please follow these steps:
- Fork the repository
- Create a feature branch:
git checkout -b feature/my-new-feature - Make your changes
- Run checks:
composer test && composer analyse - Commit your changes:
git commit -am 'Add some feature' - Push to the branch:
git push origin feature/my-new-feature - Submit a Pull Request
Running Tests
composer test
Running Static Analysis
composer analyse
GitHub Actions now runs both PHPUnit and PHPStan as part of the default CI pipeline.
The static analysis gate currently focuses on route files, notification delivery,
and the crowdsec:doctor command to keep the check lightweight across the supported
Laravel version matrix.
Security
If you discover any security-related issues, please email the maintainer instead of opening an issue.
License
This package is open-sourced software licensed under the MIT license.