Gatekeeper inqaba-security/gatekeeper
v1.x

Custom Events

Beyond the automatic monitors, any security-relevant action in your application can be reported through the Siem facade — data exports, permission changes, admin impersonation, API key creation, payment anomalies.

The one-liner: Siem::log()#

use Inqaba\Gatekeeper\Facades\Siem;
use Inqaba\Gatekeeper\Events\Severity;

Siem::log(
    'export_download',                          // event.action
    'Customer data export downloaded',          // message
    Severity::High,                             // severity (default: Medium)
    ['rows' => 12000, 'format' => 'csv'],       // laravel.context
    $request->user()                            // user (optional)
);

Request context (IP, user agent, URL, method) is captured automatically when a request is available. The action lands in event.action, so your SOC can alert on it exactly like a built-in event.

The fluent builder: Siem::event()#

For catalog event types, or when you need finer control:

use Inqaba\Gatekeeper\Events\EventType;

Siem::event(EventType::AccessDenied)
    ->user($request->user())
    ->fromRequest($request)
    ->outcome('failure')
    ->with(['ability' => 'orders.export', 'order_id' => $order->id])
    ->report();

Builder methods:

Method Purpose
severity(Severity $s) Override the type's default severity
message(string $m) Override the default human-readable message
outcome(string $o) success, failure or unknown (ECS event.outcome)
user(?Authenticatable $u) Attach user.id / user.name
fromRequest(?Request $r) Capture IP, user agent, URL, method
with(array $context) Merge into laravel.context (redacted before shipping)
report() Ship it

A real-world example: authorization failures#

Laravel doesn't fire an event when a policy denies access, but a Gate::after hook makes every denial SOC-visible:

// AppServiceProvider::boot()
use Illuminate\Support\Facades\Gate;
use Inqaba\Gatekeeper\Facades\Siem;
use Inqaba\Gatekeeper\Events\EventType;

Gate::after(function ($user, string $ability, ?bool $result) {
    if ($result === false) {
        Siem::event(EventType::AccessDenied)
            ->user($user)
            ->fromRequest(request())
            ->with(['ability' => $ability])
            ->report();
    }
});

Now repeated access_denied events from one user probing abilities they don't have becomes a hunting query in your SIEM.

Other places worth instrumenting#

  • Sensitive reads — bulk exports, report downloads, PII access
  • Privilege changes — role grants, permission edits, admin impersonation start/stop
  • Credential lifecycle — API token created/revoked, 2FA disabled, recovery codes viewed
  • Payment anomalies — refund storms, failed charge bursts, currency mismatches
  • Tenant boundaries — any cross-tenant access attempt in a multi-tenant app

Blocking IPs from your own logic#

The blocklist is exposed on the facade, so your own detections can feed it:

Siem::blockIp('203.0.113.7', minutes: 120, reason: 'fraud-model-score');
Siem::unblockIp('203.0.113.7');
Siem::blocklist()->isBlocked('203.0.113.7');   // bool
Siem::blocklist()->entry('203.0.113.7');       // ['reason' => ..., 'blocked_at' => ...]

IPs and CIDR ranges listed in blocklist.never_block are silently refused — health checkers and office ranges can never be locked out. See IP Blocklist.