Introduction and context
If you host Laravel applications exposed to the internet, you already know: bots never stop. As soon as a site goes live, it gets scanned for sensitive files: .env, wp-admin, .git, phpinfo.php. Even if your application has nothing to do with WordPress!
I was looking for a clean way to detect these reconnaissance attempts, log them, and react automatically: ban the IP via Cloudflare, report it to AbuseIPDB. Nothing existing in the Laravel ecosystem did exactly that. So I created HoneypotPlus.
Warning: HoneypotPlus is not a form honeypot. If you are looking to protect your forms against spam, check out spatie/laravel-honeypot instead. HoneypotPlus is about detecting network reconnaissance on sensitive paths.
What the package does
The principle is simple: you declare a list of "trap" paths. Any IP that tries to access one of these paths is immediately considered malicious.
Concretely, HoneypotPlus:
- Detects access to sensitive paths (
.env,wp-admin,.git, etc.) via a middleware - Logs the attack to the database (IP, path, user-agent, HTTP method, timestamp)
- Automatically bans the IP via the Cloudflare Firewall API (if credentials are present)
- Reports the IP to AbuseIPDB (if the API key is present)
- Automatically cleans up expired bans via the Laravel scheduler
- Exposes an interactive CLI interface to manage blocked IPs
The Cloudflare and AbuseIPDB integrations are optional and auto-enable as soon as the corresponding environment variables are present. Zero extra configuration.
Installation
Install via Composer:
composer require ilogus/laravel-honeypotplus
Then run the install command which publishes the config and migration:
php artisan honeypot-plus:install
Add the middleware in bootstrap/app.php:
use HoneypotPlus\Middleware\HoneypotPlusMiddleware;
return Application::configure(basePath: dirname(__DIR__))
->withMiddleware(function (Middleware $middleware) {
$middleware->append(HoneypotPlusMiddleware::class);
})
->create();
Use
append()rather thanprepend(). The middleware must run before Laravel's routing, otherwise Laravel returns a standard 404 and the malicious IP is never detected.
Then configure the environment variables in .env:
HONEYPOT_PLUS_ENABLE=true
HONEYPOT_PLUS_LOGGING=true
HONEYPOT_PLUS_BAN_DURATION_HOURS=24
# Cloudflare (optional)
HONEYPOT_PLUS_CLOUDFLARE_API_TOKEN=your_token
HONEYPOT_PLUS_CLOUDFLARE_ZONE_ID=your_zone_id
# AbuseIPDB (optional)
HONEYPOT_PLUS_ABUSEIPDB_KEY=your_key
Customizing honeypot rules
In config/honeypot-plus.php, you can define your own trap paths. The package supports both static paths and regex patterns:
'honeypots' => [
'/.env',
'/wp-admin',
'/.git',
// Regex patterns (prefix with 'regex:')
'regex:/^\.env\./i',
'regex:/wp-config\.php$/i',
],
Event-driven architecture
This is the part that interests me most in this package: the architecture is entirely based on Laravel's event system.
When an IP triggers a trap, the middleware dispatches a HoneypotAttackDetected event:
final class HoneypotAttackDetected
{
use Dispatchable, SerializesModels;
public function __construct(
public string $ip,
public string $honeypotRule,
public ?string $userAgent,
public string $httpMethod,
public string $pathRequested,
) {}
}
The internal listener HandleHoneypotAttack handles it to log the attack, call Cloudflare, and report to AbuseIPDB.
But the real advantage is that you can write your own listeners. If you want to forward these events to Elasticsearch, Kibana, a Slack channel, a webhook, or any other tool, just subscribe to the HoneypotAttackDetected event in your EventServiceProvider:
use HoneypotPlus\Events\HoneypotAttackDetected;
use App\Listeners\ForwardAttackToElasticsearch;
protected $listen = [
HoneypotAttackDetected::class => [
ForwardAttackToElasticsearch::class,
],
];
And your listener receives all the necessary information: the IP, the triggered rule, the user-agent, the HTTP method, and the requested path. You then do whatever you want with it.
class ForwardAttackToElasticsearch implements ShouldQueue
{
public function handle(HoneypotAttackDetected $event): void
{
// Send to Elasticsearch, Kibana, Slack, etc.
ElasticsearchClient::index([
'index' => 'honeypot-attacks',
'body' => [
'ip' => $event->ip,
'rule' => $event->honeypotRule,
'path' => $event->pathRequested,
'user_agent' => $event->userAgent,
'method' => $event->httpMethod,
'detected_at' => now()->toIso8601String(),
],
]);
}
}
By implementing ShouldQueue, processing happens asynchronously. Your HTTP response is not blocked.
CLI management
The package exposes an interactive Artisan command to manage blocked IPs:
php artisan honeypot-plus:manage
Available actions:
- List blocked IPs
- Ban an IP manually
- Unban an IP
- Show statistics
You can also use the facade directly in code:
use HoneypotPlus\Facades\HoneypotPlus;
HoneypotPlus::ban('192.168.1.1', 24); // Ban for 24h
HoneypotPlus::isBanned('192.168.1.1'); // Check if banned
HoneypotPlus::unban('192.168.1.1'); // Unban
$stats = HoneypotPlus::getStats(); // Statistics
Automatic cleanup
The Laravel scheduler is configured automatically. Expired bans are cleaned up regularly without any manual intervention. To verify:
php artisan schedule:list
My takeaway
This package is primarily a practical tool I use on my own projects. The decision to go through Laravel's event system rather than monolithic processing was deliberate: it makes the package extensible without touching its source code.
If you have exposed Laravel applications, this is the kind of thing you install once and forget about, until the day you look at the stats and realize how many bots are constantly running.
The source code is available on GitHub. Contributions and feedback are welcome.