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:clearclears 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.