Gatekeeper inqaba-security/gatekeeper
v1.x

Request Threat Scanner

The siem.scan middleware runs lightweight pattern heuristics over the query string, URL and request body, flagging payloads that look like injection probes. It is telemetry, not a WAF: by default nothing is blocked — the SOC gets a suspicious_input event and the request proceeds.

Why detection-only by default? Because the interesting question isn't "did we block one probe" (your framework's parameter binding and escaping should make most probes inert) — it's "who is probing us, with what, and where". That's a SIEM question.

Usage#

// Broad deployment
$middleware->web(append: [\Inqaba\Gatekeeper\Http\Middleware\ScanRequestThreats::class]);

// Or targeted
Route::middleware('siem.scan')->group(...);

Detection rules#

Rule Catches
sql_injection UNION SELECT, boolean tautologies with comment suffixes, SLEEP() / BENCHMARK() / WAITFOR DELAY / pg_sleep() time-based probes
xss_probe <script>, event-handler attributes (onerror=, onload=, ...), javascript: URLs, document.cookie access, <iframe> / <svg onload>
path_traversal repeated ../ sequences (raw and URL-encoded), /etc/passwd, C:\Windows
command_injection shell metacharacters chained to commands (; cat, | whoami, `id`, $(curl ...)), ${IFS}
template_injection {{ 7*7 }}-style SSTI probes, {{ config }} / {{ self }} Twig/Blade probing
jndi_injection ${jndi:ldap://...} log4shell-style payloads
php_wrapper php://filter, php://input, expect://, phar:// stream wrappers
null_byte %00 and escaped null-byte injection

Each flagged input produces one finding (first matching rule wins), reported by input key, never value — the SIEM learns that q contained a SQLi probe, not the probe itself (see Privacy):

{
    "event": { "action": "suspicious_input", "severity": 7 },
    "threat": { "technique": { "id": ["T1190"], "name": ["Exploit Public-Facing Application"] } },
    "laravel": {
        "context": {
            "findings": [
                { "rule": "sql_injection", "input": "q" },
                { "rule": "path_traversal", "input": "__url_path" }
            ]
        }
    }
}

__url_path means the match was in the request URI itself rather than an input field.

Blocking mode#

SIEM_SCANNER_BLOCK=true

Flagged requests are rejected with 400 (JSON-aware). Turn this on only after watching the suspicious_input stream for false positives in your traffic — a code-snippet field on a developer forum will trip xss_probe legitimately.

Avoiding false positives#

  • except_inputs — input keys skipped entirely (matched on the leaf key, so user.password is covered by password). Defaults: password, password_confirmation. Add any field that legitimately contains markup, code or SQL:
'scanner' => [
    'except_inputs' => ['password', 'password_confirmation', 'body_html', 'code_sample'],
],
  • max_value_length (default 8192) — only the first N characters of each value are scanned, bounding CPU on large payloads.
  • Nested input is flattened with dot notation (items.0.note), so findings pinpoint the exact field in array payloads.

Configuration#

Key Env Default
scanner.block SIEM_SCANNER_BLOCK false
scanner.except_inputs password, password_confirmation
scanner.max_value_length 8192

Honest limitations#

Pattern heuristics catch probes — the noisy reconnaissance phase where attackers spray known payloads. A skilled attacker crafting an app-specific exploit can evade regex. That's fine: the scanner's job is early warning and attribution ("IP X is testing our search parameter for SQLi"), layered under parameterized queries, escaping and a real WAF — not replacing them.