Gatekeeper inqaba-security/gatekeeper
v1.x

IP Blocklist

A cache-backed, TTL'd blocklist that ties the detection features together: honeypots and abuse/enumeration detections feed it, the siem.block middleware enforces it, and the Siem facade manages it manually. It's temporary containment — entries expire automatically — not a firewall replacement.

Enforcement#

Register siem.block first in the stack, so blocked IPs are rejected before any other work:

// bootstrap/app.php (Laravel 11+)
$middleware->prepend(\Inqaba\Gatekeeper\Http\Middleware\BlockDeniedIps::class);

Blocked requests receive 403 and emit a blocked_ip_attempt event — throttled to one event per IP per 5 minutes, so a persistent attacker documents their own persistence without flooding the SIEM:

{
    "event": { "action": "blocked_ip_attempt", "severity": 3 },
    "source": { "ip": "203.0.113.9" },
    "laravel": {
        "context": { "block_reason": "honeypot:wp-login.php", "blocked_at": "2026-07-11T09:14:23+00:00" }
    }
}

The block_reason tells the analyst why the IP was blocked: honeypot:<path>, abuse:<detection>, enumeration:<pattern>, or whatever your code supplied.

Who feeds the blocklist#

Source Default Duration
Route honeypots on 24 h
Abuse detection off (SIEM_ABUSE_AUTOBLOCK) 60 min
Enumeration detection off (SIEM_ENUM_AUTOBLOCK) 60 min
Your code your choice

Manual management#

use Inqaba\Gatekeeper\Facades\Siem;

Siem::blockIp('203.0.113.7', minutes: 120, reason: 'analyst-decision');
Siem::unblockIp('203.0.113.7');

Siem::blocklist()->isBlocked('203.0.113.7');  // bool
Siem::blocklist()->entry('203.0.113.7');      // ['reason' => ..., 'blocked_at' => ...] | null
Siem::blocklist()->isExempt('10.1.2.3');      // bool

blockIp() returns quietly without blocking if the IP is exempt.

The never-block list#

Protect your own infrastructure from ever being auto-blocked — load balancer health checks, uptime monitors, office ranges, CI runners:

SIEM_NEVER_BLOCK=127.0.0.1,::1,10.0.0.0/8,196.25.10.0/24

Both exact IPs and CIDR ranges are supported, IPv4 and IPv6. Exemption beats everything: a health checker that trips a honeypot is logged (the event still ships) but never blocked.

Storage#

Entries live in your cache under siem:blocked:<ip> with the block duration as TTL — expiry is automatic, no scheduler needed. Use a shared store in multi-server setups so a block on one web head applies to all:

SIEM_CACHE_STORE=redis
Key Env Default
blocklist.enabled SIEM_BLOCKLIST true
blocklist.cache_store SIEM_CACHE_STORE app default
blocklist.response_status 403
blocklist.never_block SIEM_NEVER_BLOCK 127.0.0.1,::1

Design notes#

  • Temporary by design. Auto-blocks expire because IP reputation decays fast — residential IPs rotate, VPN exits are shared, and permanent IP bans accumulate collateral damage. For permanent bans, use your firewall or CDN, driven by SIEM analysis.
  • Cache flush = blocklist flush. php artisan cache:clear clears active blocks. Acceptable for containment; another reason long-term bans belong at the edge.
  • Behind a proxy/CDN, configure trusted proxies or every block will hit the proxy IP — see the note in Endpoint Abuse.