HoneypotPlus: my Laravel package to detect and ban malicious IPs

Detect bots scanning your Laravel applications on sensitive paths (.env, wp-admin, .git), ban them via Cloudflare and report them to AbuseIPDB. Discover HoneypotPlus, an event-driven, zero-configuration Laravel package.

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 than prepend(). 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.